From ca44037322fa95e31ec6bf431ca80670c14f2afb Mon Sep 17 00:00:00 2001 From: Jason Barnett Date: Wed, 22 May 2019 21:17:30 -0400 Subject: [PATCH 01/65] Add RunListItem struct --- run_list_item.go | 88 +++++++++++++++++++++++ run_list_item_test.go | 164 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 run_list_item.go create mode 100644 run_list_item_test.go diff --git a/run_list_item.go b/run_list_item.go new file mode 100644 index 0000000..757ce61 --- /dev/null +++ b/run_list_item.go @@ -0,0 +1,88 @@ +package chef + +import ( + "fmt" + "regexp" +) + +const ( + qualifiedRecipe string = `^recipe\[([^\]@]+)(@([0-9]+(\.[0-9]+){1,2}))?\]$` + qualifiedRole string = `^role\[([^\]]+)\]$` + versionedUnqualifiedRecipe string = `^([^@]+)(@([0-9]+(\.[0-9]+){1,2}))$` + falseFriend string = `[\[\]]` +) + +var ( + qualifiedRecipeRegexp *regexp.Regexp = regexp.MustCompile(qualifiedRecipe) + qualifiedRoleRegexp *regexp.Regexp = regexp.MustCompile(qualifiedRole) + versionedUnqualifiedRecipeRegexp *regexp.Regexp = regexp.MustCompile(versionedUnqualifiedRecipe) + falseFriendRegexp *regexp.Regexp = regexp.MustCompile(falseFriend) +) + +// This is a direct port of the Chef::RunList::RunListItem class +// see: https://github.com/chef/chef/blob/master/lib/chef/run_list/run_list_item.rb +type RunListItem struct { + Name string + Type string + Version string +} + +func NewRunListItem(item string) (rli RunListItem, err error) { + switch { + case qualifiedRecipeRegexp.MatchString(item): + // recipe[name] + // recipe[name@1.0.0] + rli.Type = "recipe" + + submatches := qualifiedRecipeRegexp.FindStringSubmatch(item) + rli.Name = submatches[1] + + if len(submatches) > 2 { + rli.Version = submatches[3] + } + case qualifiedRoleRegexp.MatchString(item): + // role[role_name] + rli.Type = "role" + + submatches := qualifiedRoleRegexp.FindStringSubmatch(item) + rli.Name = submatches[1] + case versionedUnqualifiedRecipeRegexp.MatchString(item): + // recipe_name@1.0.0 + rli.Type = "recipe" + submatches := versionedUnqualifiedRecipeRegexp.FindStringSubmatch(item) + + rli.Name = submatches[1] + + if len(submatches) > 2 { + rli.Version = submatches[3] + } + case falseFriendRegexp.MatchString(item): + // Recipe[recipe_name] + // roles[role_name] + err = fmt.Errorf("Invalid run-list item: %s", item) + return RunListItem{}, err + default: + rli.Type = "recipe" + rli.Name = item + } + + return rli, nil +} + +func (r RunListItem) String() (s string) { + if r.Version != "" { + s = fmt.Sprintf("%s[%s@%s]", r.Type, r.Name, r.Version) + } else { + s = fmt.Sprintf("%s[%s]", r.Type, r.Name) + } + + return s +} + +func (r RunListItem) IsRecipe() bool { + return r.Type == "recipe" +} + +func (r RunListItem) IsRole() bool { + return r.Type == "role" +} diff --git a/run_list_item_test.go b/run_list_item_test.go new file mode 100644 index 0000000..8c7a742 --- /dev/null +++ b/run_list_item_test.go @@ -0,0 +1,164 @@ +package chef + +import ( + "testing" +) + +func TestQualifiedRecipeWithVersion(t *testing.T) { + runListItem := "recipe[my_recipe@1.0.0]" + rli, err := NewRunListItem(runListItem) + if err != nil { + t.Errorf(`NewRunListItem("%s") did not correctly parse.`, runListItem) + } + if rli.Name != "my_recipe" { + t.Errorf(`NewRunListItem("%s") did not correctly set name`, runListItem) + } + if rli.Type != "recipe" { + t.Errorf(`NewRunListItem("%s") did not correctly set type`, runListItem) + } + if rli.Version != "1.0.0" { + t.Errorf(`NewRunListItem("%s") did not correctly set version`, runListItem) + } +} + +func TestQualifiedRecipe(t *testing.T) { + runListItem := "recipe[my_recipe]" + rli, err := NewRunListItem(runListItem) + if err != nil { + t.Errorf(`NewRunListItem("%s") did not correctly parse.`, runListItem) + } + if rli.Name != "my_recipe" { + t.Errorf(`NewRunListItem("%s") did not correctly set name`, runListItem) + } + if rli.Type != "recipe" { + t.Errorf(`NewRunListItem("%s") did not correctly set type`, runListItem) + } + if rli.Version != "" { + t.Errorf(`NewRunListItem("%s") did not correctly set version`, runListItem) + } +} + +func TestQualifiedRole(t *testing.T) { + runListItem := "role[my_role]" + rli, err := NewRunListItem(runListItem) + if err != nil { + t.Errorf(`NewRunListItem("%s") did not correctly parse.`, runListItem) + } + if rli.Name != "my_role" { + t.Errorf(`NewRunListItem("%s") did not correctly set name`, runListItem) + } + if rli.Type != "role" { + t.Errorf(`NewRunListItem("%s") did not correctly set type, %s`, runListItem, rli.Type) + } + if rli.Version != "" { + t.Errorf(`NewRunListItem("%s") did not correctly set version`, runListItem) + } +} + +func TestVersionedUnqualifiedRecipe(t *testing.T) { + runListItem := "my_recipe@1.0.0" + rli, err := NewRunListItem(runListItem) + if err != nil { + t.Errorf(`NewRunListItem("%s") did not correctly parse.`, runListItem) + } + if rli.Name != "my_recipe" { + t.Errorf(`NewRunListItem("%s") did not correctly set name`, runListItem) + } + if rli.Type != "recipe" { + t.Errorf(`NewRunListItem("%s") did not correctly set type`, runListItem) + } + if rli.Version != "1.0.0" { + t.Errorf(`NewRunListItem("%s") did not correctly set version`, runListItem) + } +} + +func TestRecipeNameAlone(t *testing.T) { + runListItem := "my_recipe" + rli, err := NewRunListItem(runListItem) + if err != nil { + t.Errorf(`NewRunListItem("%s") did not correctly parse.`, runListItem) + } + if rli.Name != "my_recipe" { + t.Errorf(`NewRunListItem("%s") did not correctly set name`, runListItem) + } + if rli.Type != "recipe" { + t.Errorf(`NewRunListItem("%s") did not correctly set type`, runListItem) + } + if rli.Version != "" { + t.Errorf(`NewRunListItem("%s") did not correctly set version`, runListItem) + } +} + +func TestBadCase(t *testing.T) { + runListItem := "Recipe[my_recipe]" // Capital R + _, err := NewRunListItem(runListItem) + + if err == nil { + t.Errorf(`NewRunListItem("%s") should have returned and error and it didn't.`, runListItem) + } +} + +func TestSpaceThrowsError(t *testing.T) { + runListItem := "recipe [my_recipe]" + _, err := NewRunListItem(runListItem) + + if err == nil { + t.Errorf(`NewRunListItem("%s") should have returned and error and it didn't.`, runListItem) + } +} + +func TestMissingClosingBracketThrowsError(t *testing.T) { + runListItem := "recipe[my_recipe" + _, err := NewRunListItem(runListItem) + + if err == nil { + t.Errorf(`NewRunListItem("%s") should have returned and error and it didn't.`, runListItem) + } +} + +func TestStringConversion(t *testing.T) { + runListItems := []string{ + "recipe[my_recipe@1.0.0]", + "recipe[my_recipe]", + "role[my_recipe]", + } + + for _, runListItem := range runListItems { + rli, err := NewRunListItem(runListItem) + if err != nil || rli.String() != runListItem { + t.Errorf(`NewRunListItem("%s").String() does not match %s`, runListItem, runListItem) + } + } +} + +func TestIsRecipe(t *testing.T) { + recipe := RunListItem{ + Type: "recipe", + } + if recipe.IsRecipe() != true { + t.Error(`IsRecipe() should return true for recipe`) + } + + role := RunListItem{ + Type: "role", + } + if role.IsRecipe() != false { + t.Error(`IsRecipe() should return false for role`) + } +} + +func TestIsRole(t *testing.T) { + recipe := RunListItem{ + Type: "role", + } + if recipe.IsRole() != true { + t.Error(`IsRole() should return true for role`) + } + + role := RunListItem{ + Type: "recipe", + } + if role.IsRole() != false { + t.Error(`IsRole() should return false for recipe`) + } +} From 0ace282652e240408001f6dd47a007abe3df047b Mon Sep 17 00:00:00 2001 From: Salim Afiune Date: Mon, 21 Oct 2019 17:21:47 -0600 Subject: [PATCH 02/65] Use checksum to verify cookbook integrity https://github.com/chef/go-chef/issues/3 Signed-off-by: Salim Afiune --- .studiorc | 12 +++++++++++ cookbook_download.go | 42 +++++++++++++++++++++++++++++++++------ cookbook_download_test.go | 22 ++++++++++++++++++-- 3 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 .studiorc diff --git a/.studiorc b/.studiorc new file mode 100644 index 0000000..2155486 --- /dev/null +++ b/.studiorc @@ -0,0 +1,12 @@ +#!/bin/bash +# +# This is the place you can extend the funcitonality of the studio + +hab pkg install chef/studio-common >/dev/null +source "$(hab pkg path chef/studio-common)/bin/studio-common" + +function run_tests() { + install_if_missing core/go go + install_if_missing core/gcc gcc + go test +} diff --git a/cookbook_download.go b/cookbook_download.go index 8f982f9..1cc5aa0 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -5,6 +5,9 @@ package chef import ( + "crypto/md5" + "errors" + "fmt" "io" "os" "path" @@ -73,8 +76,7 @@ func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType, } for _, item := range items { - itemPath := path.Join(localPath, item.Name) - if err := c.downloadCookbookFile(item.Url, itemPath); err != nil { + if err := c.downloadCookbookFile(item, localPath); err != nil { return err } } @@ -83,11 +85,14 @@ func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType, } // downloadCookbookFile downloads a single cookbook file to disk -func (c *CookbookService) downloadCookbookFile(url, file string) error { - request, err := c.client.NewRequest("GET", url, nil) +func (c *CookbookService) downloadCookbookFile(item CookbookItem, localPath string) error { + filePath := path.Join(localPath, item.Name) + + request, err := c.client.NewRequest("GET", item.Url, nil) if err != nil { return err } + response, err := c.client.Do(request, nil) if response != nil { defer response.Body.Close() @@ -96,13 +101,38 @@ func (c *CookbookService) downloadCookbookFile(url, file string) error { return err } - f, err := os.Create(file) + f, err := os.Create(filePath) if err != nil { return err } + defer f.Close() if _, err := io.Copy(f, response.Body); err != nil { return err } - return nil + + if verifyMD5Checksum(filePath, item.Checksum) { + return nil + } + + return errors.New("wrong checksum") +} + +func verifyMD5Checksum(filePath, checksum string) bool { + file, err := os.Open(filePath) + if err != nil { + return false + } + defer file.Close() + + hash := md5.New() + if _, err := io.Copy(hash, file); err != nil { + return false + } + + md5String := fmt.Sprintf("%x", hash.Sum(nil)) + if md5String == checksum { + return true + } + return false } diff --git a/cookbook_download_test.go b/cookbook_download_test.go index d5708b3..f8df313 100644 --- a/cookbook_download_test.go +++ b/cookbook_download_test.go @@ -93,7 +93,7 @@ func TestCookbooksDownloadAt(t *testing.T) { { "name": "default.rb", "path": "recipes/default.rb", - "checksum": "320sdk2w38020827kdlsdkasbd5454b6", + "checksum": "8e751ed8663cb9b97499956b6a20b0de", "specificity": "default", "url": "` + server.URL + `/bookshelf/foo/default_rb" } @@ -103,7 +103,7 @@ func TestCookbooksDownloadAt(t *testing.T) { { "name": "metadata.rb", "path": "metadata.rb", - "checksum": "14963c5b685f3a15ea90ae51bd5454b6", + "checksum": "6607f3131919e82dc4ba4c026fcfee9f", "specificity": "default", "url": "` + server.URL + `/bookshelf/foo/metadata_rb" } @@ -152,3 +152,21 @@ func TestCookbooksDownloadAt(t *testing.T) { assert.Equal(t, "log 'this is a resource'", string(recipeBytes)) } } + +func TestVerifyMD5Checksum(t *testing.T) { + tempDir, err := ioutil.TempDir("", "md5-test") + if err != nil { + t.Error(err) + } + defer os.RemoveAll(tempDir) // clean up + + var ( + // if someone changes the test data, + // you have to also update the below md5 sum + testData = []byte("hello\nchef\n") + filePath = path.Join(tempDir, "dat") + ) + err = ioutil.WriteFile(filePath, testData, 0644) + assert.Nil(t, err) + assert.True(t, verifyMD5Checksum(filePath, "70bda176ac4db06f1f66f96ae0693be1")) +} From 98a358cad1929ce8234c0a56939dab8fa86584c1 Mon Sep 17 00:00:00 2001 From: Salim Afiune Date: Tue, 22 Oct 2019 13:02:45 -0600 Subject: [PATCH 03/65] Better error message on checksum mismatch Signed-off-by: Salim Afiune --- cookbook_download.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cookbook_download.go b/cookbook_download.go index 1cc5aa0..0ef102d 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -6,7 +6,6 @@ package chef import ( "crypto/md5" - "errors" "fmt" "io" "os" @@ -115,7 +114,11 @@ func (c *CookbookService) downloadCookbookFile(item CookbookItem, localPath stri return nil } - return errors.New("wrong checksum") + return fmt.Errorf( + "cookbook file '%s' checksum mismatch. (expected:%s)", + filePath, + item.Checksum, + ) } func verifyMD5Checksum(filePath, checksum string) bool { From 0500dcaecd1111ba873e09aef8e78cd300dc41ad Mon Sep 17 00:00:00 2001 From: Salim Afiune Date: Thu, 17 Oct 2019 12:53:03 -0600 Subject: [PATCH 04/65] Rename DownloadAt() to DownloadTo() Signed-off-by: Salim Afiune --- cookbook_download.go | 6 +++--- cookbook_download_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cookbook_download.go b/cookbook_download.go index 0ef102d..2d7635c 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -19,11 +19,11 @@ func (c *CookbookService) Download(name, version string) error { return err } - return c.DownloadAt(name, version, cwd) + return c.DownloadTo(name, version, cwd) } -// DownloadAt downloads a cookbook to the specified local directory on disk -func (c *CookbookService) DownloadAt(name, version, localDir string) error { +// DownloadTo downloads a cookbook to the specified local directory on disk +func (c *CookbookService) DownloadTo(name, version, localDir string) error { // If the version is set to 'latest' or it is empty ("") then, // we will set the version to '_latest' which is the default endpoint if version == "" || version == "latest" { diff --git a/cookbook_download_test.go b/cookbook_download_test.go index f8df313..202b942 100644 --- a/cookbook_download_test.go +++ b/cookbook_download_test.go @@ -72,7 +72,7 @@ func TestCookbooksDownloadEmptyWithVersion(t *testing.T) { assert.Nil(t, err) } -func TestCookbooksDownloadAt(t *testing.T) { +func TestCookbooksDownloadTo(t *testing.T) { setup() defer teardown() @@ -130,7 +130,7 @@ func TestCookbooksDownloadAt(t *testing.T) { fmt.Fprintf(w, "log 'this is a resource'") }) - err = client.Cookbooks.DownloadAt("foo", "0.2.1", tempDir) + err = client.Cookbooks.DownloadTo("foo", "0.2.1", tempDir) assert.Nil(t, err) var ( From 494c1833d0752e2dadf788ef42e1be76a66d27ef Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 11 Nov 2019 20:15:50 -0800 Subject: [PATCH 05/65] Add the initial circleci config file --- .circleci/config.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..a2328c0 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,26 @@ +# Golang CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-go/ for more details +version: 2 +jobs: + build: + docker: + # specify the version + - image: circleci/golang:1.9 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + + #### TEMPLATE_NOTE: go expects specific checkout path representing url + #### expecting it in the form of + #### /go/src/github.com/circleci/go-tool + #### /go/src/bitbucket.org/circleci/go-tool + working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}} + steps: + - checkout + + # specify any bash command here prefixed with `run: ` + - run: go get -v -t -d ./... + - run: go test -v ./... From d32e9a44d5f9bbcbc68c7d5165973356ed7fd560 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 30 Nov 2019 20:55:12 -0800 Subject: [PATCH 06/65] Keep DownloadAt as an alias for DownloadTo --- cookbook_download.go | 6 +++ cookbook_download_test.go | 81 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/cookbook_download.go b/cookbook_download.go index 2d7635c..91f8b33 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -62,6 +62,12 @@ func (c *CookbookService) DownloadTo(name, version, localDir string) error { return nil } +// DownloadAt is a deprecated alias for DownloadTo +func (c *CookbookService) DownloadAt(name, version, localDir string) error { + err := c.DownloadTo(name, version, localDir) + return err +} + // downloadCookbookItems downloads all the provided cookbook items into the provided // local path, it also ensures that the provided directory exists by creating it func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType, localPath string) error { diff --git a/cookbook_download_test.go b/cookbook_download_test.go index 202b942..9b6b1f2 100644 --- a/cookbook_download_test.go +++ b/cookbook_download_test.go @@ -153,6 +153,87 @@ func TestCookbooksDownloadTo(t *testing.T) { } } +func TestCookbooksDownloadAt(t *testing.T) { + setup() + defer teardown() + + mockedCookbookResponseFile := ` +{ + "version": "0.2.1", + "name": "foo-0.2.1", + "cookbook_name": "foo", + "frozen?": false, + "chef_type": "cookbook_version", + "json_class": "Chef::CookbookVersion", + "attributes": [], + "definitions": [], + "files": [], + "libraries": [], + "providers": [], + "recipes": [ + { + "name": "default.rb", + "path": "recipes/default.rb", + "checksum": "8e751ed8663cb9b97499956b6a20b0de", + "specificity": "default", + "url": "` + server.URL + `/bookshelf/foo/default_rb" + } + ], + "resources": [], + "root_files": [ + { + "name": "metadata.rb", + "path": "metadata.rb", + "checksum": "6607f3131919e82dc4ba4c026fcfee9f", + "specificity": "default", + "url": "` + server.URL + `/bookshelf/foo/metadata_rb" + } + ], + "templates": [], + "metadata": {}, + "access": {} +} +` + + tempDir, err := ioutil.TempDir("", "foo-cookbook") + if err != nil { + t.Error(err) + } + defer os.RemoveAll(tempDir) // clean up + + mux.HandleFunc("/cookbooks/foo/0.2.1", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, string(mockedCookbookResponseFile)) + }) + mux.HandleFunc("/bookshelf/foo/metadata_rb", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "name 'foo'") + }) + mux.HandleFunc("/bookshelf/foo/default_rb", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "log 'this is a resource'") + }) + + err = client.Cookbooks.DownloadAt("foo", "0.2.1", tempDir) + assert.Nil(t, err) + + var ( + cookbookPath = path.Join(tempDir, "foo-0.2.1") + metadataPath = path.Join(cookbookPath, "metadata.rb") + recipesPath = path.Join(cookbookPath, "recipes") + defaultPath = path.Join(recipesPath, "default.rb") + ) + assert.DirExistsf(t, cookbookPath, "the cookbook directory should exist") + assert.DirExistsf(t, recipesPath, "the recipes directory should exist") + if assert.FileExistsf(t, metadataPath, "a metadata.rb file should exist") { + metadataBytes, err := ioutil.ReadFile(metadataPath) + assert.Nil(t, err) + assert.Equal(t, "name 'foo'", string(metadataBytes)) + } + if assert.FileExistsf(t, defaultPath, "the default.rb recipes should exist") { + recipeBytes, err := ioutil.ReadFile(defaultPath) + assert.Nil(t, err) + assert.Equal(t, "log 'this is a resource'", string(recipeBytes)) + } +} + func TestVerifyMD5Checksum(t *testing.T) { tempDir, err := ioutil.TempDir("", "md5-test") if err != nil { From 8a98980cb510aa6566a440decadc32092086aad4 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 30 Nov 2019 21:11:52 -0800 Subject: [PATCH 07/65] Remove .studiorc --- .studiorc | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .studiorc diff --git a/.studiorc b/.studiorc deleted file mode 100644 index 2155486..0000000 --- a/.studiorc +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -# -# This is the place you can extend the funcitonality of the studio - -hab pkg install chef/studio-common >/dev/null -source "$(hab pkg path chef/studio-common)/bin/studio-common" - -function run_tests() { - install_if_missing core/go go - install_if_missing core/gcc gcc - go test -} From 16d4f74c7272ac244d52c89e99cdb845b4866780 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Thu, 5 Dec 2019 10:01:25 -0800 Subject: [PATCH 08/65] Update the tag for the release as it is merged to master Use the autotag code --- .circleci/config.yml | 50 ++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a2328c0..88cad3e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,25 +2,43 @@ # # Check https://circleci.com/docs/2.0/language-go/ for more details version: 2 +workflows: + version: 2 + merge-to-master: + jobs: + - tester + - update_tag: + requires: + - tester + filters: + branches: + only: /master/ jobs: - build: + update_tag: docker: - # specify the version - image: circleci/golang:1.9 - - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/postgres:9.4 - - #### TEMPLATE_NOTE: go expects specific checkout path representing url - #### expecting it in the form of - #### /go/src/github.com/circleci/go-tool - #### /go/src/bitbucket.org/circleci/go-tool - working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}} + working_directory: /go/src/github.com/go-chef/chefi steps: + - add_ssh_keys: + fingerprints: + - 35:45:41:ba:cf:3f:7f:d5:00:0f:11:6b:4d:c0:a1:90 + - checkout + - run: curl -s https://api.github.com/repos/pantheon-systems/autotag/releases/latest | grep browser_download | grep Linux | cut -d '"' -f 4 | xargs curl -o ./autotag -L && chmod 755 ./autotag + - run: ./autotag + - run: git push --tags origin + tester: + docker: + - image: circleci/golang:1.9 + #### TEMPLATE_NOTE: go expects specific checkout path representing url + #### expecting it in the form of + #### /go/src/github.com/circleci/go-tool + #### /go/src/bitbucket.org/circleci/go-tool + working_directory: /go/src/github.com/go-chef/chef + steps: + - add_ssh_keys: + fingerprints: + - 35:45:41:ba:cf:3f:7f:d5:00:0f:11:6b:4d:c0:a1:90 - checkout - # specify any bash command here prefixed with `run: ` - - run: go get -v -t -d ./... - - run: go test -v ./... + - run: env + - run: ls && pwd From 01bc16989c1ae20bc282c3841db219e37ef5a78a Mon Sep 17 00:00:00 2001 From: markgibbons Date: Fri, 6 Dec 2019 11:35:26 -0800 Subject: [PATCH 09/65] SSH key used for tagging accessed via variable --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 88cad3e..98dbe73 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,7 +21,7 @@ jobs: steps: - add_ssh_keys: fingerprints: - - 35:45:41:ba:cf:3f:7f:d5:00:0f:11:6b:4d:c0:a1:90 + - ${TAG_TOKEN} - checkout - run: curl -s https://api.github.com/repos/pantheon-systems/autotag/releases/latest | grep browser_download | grep Linux | cut -d '"' -f 4 | xargs curl -o ./autotag -L && chmod 755 ./autotag - run: ./autotag From a2322d6aa908eceebda280b99f13d19da0ce1943 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Fri, 6 Dec 2019 20:31:36 -0800 Subject: [PATCH 10/65] Add go get and fmt to the pipeline tester --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 98dbe73..69a995c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,3 +42,5 @@ jobs: # specify any bash command here prefixed with `run: ` - run: env - run: ls && pwd + - run: go get + - run: go fmt From 309ee522f444d697bd7ae97f93c3b240b2fc1fcb Mon Sep 17 00:00:00 2001 From: markgibbons Date: Fri, 6 Dec 2019 20:35:32 -0800 Subject: [PATCH 11/65] Add go vet and test to the pipline tester job --- .circleci/config.yml | 11 ++++++++--- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 69a995c..ed725d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,10 +37,15 @@ jobs: steps: - add_ssh_keys: fingerprints: - - 35:45:41:ba:cf:3f:7f:d5:00:0f:11:6b:4d:c0:a1:90 + - 35:v45:41:ba:cf:3f:7f:d5:00:0f:11:6b:4d:c0:a1:90 - checkout # specify any bash command here prefixed with `run: ` - run: env - run: ls && pwd - - run: go get - - run: go fmt + - run: go get -v ./... + - run: go get github.com/ctdk/goiardi/chefcrypto + - run: go get github.com/smartystreets/goconvey/convey + - run: go get github.com/stretchr/testify/assert + - run: go fmt ./... + - run: go vet ./... + - run: go test ./... diff --git a/go.mod b/go.mod index 8a946cf..b28e1a5 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.12 require ( github.com/cenkalti/backoff v2.1.1+incompatible - github.com/ctdk/goiardi v0.11.9 + github.com/ctdk/goiardi v0.11.10 github.com/davecgh/go-spew v1.1.1 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index 3941315..0aec861 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1q github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/ctdk/goiardi v0.11.9 h1:OkrfamLJkktXQGc6buUvg+VMd2L3F/63zrhJCINZE8s= github.com/ctdk/goiardi v0.11.9/go.mod h1:Pr6Cj6Wsahw45myttaOEZeZ0LE7p1qzWmzgsBISkrNI= +github.com/ctdk/goiardi v0.11.10 h1:IB/3Afl1pC2Q4KGwzmhHPAoJfe8VtU51wZ2V0QkvsL0= +github.com/ctdk/goiardi v0.11.10/go.mod h1:Pr6Cj6Wsahw45myttaOEZeZ0LE7p1qzWmzgsBISkrNI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From b8a5b9cbb7b472eba5c1e2759aa6c288b8251de1 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 7 Dec 2019 07:10:13 -0800 Subject: [PATCH 12/65] Fix the intermittent failures in the String tests --- .circleci/config.yml | 2 +- client_test.go | 8 +++++--- databag_test.go | 7 +++++-- environment_test.go | 11 +++++++---- role_test.go | 10 ++++++---- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ed725d7..57a91e4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,6 +46,6 @@ jobs: - run: go get github.com/ctdk/goiardi/chefcrypto - run: go get github.com/smartystreets/goconvey/convey - run: go get github.com/stretchr/testify/assert - - run: go fmt ./... + - run: gofmt -l -d *.go && test -z $(go fmt ./....) - run: go vet ./... - run: go test ./... diff --git a/client_test.go b/client_test.go index ad7aff7..7daeeaf 100644 --- a/client_test.go +++ b/client_test.go @@ -40,15 +40,17 @@ func TestClientsService_List(t *testing.T) { mux.HandleFunc("/clients", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `{"client1": "http://localhost/clients/client1", "client2": "http://localhost/clients/client2"}`) }) - response, err := client.Clients.List() if err != nil { t.Errorf("Clients.List returned error: %v", err) } + // The order printed by the String function is not defined want := "client1 => http://localhost/clients/client1\nclient2 => http://localhost/clients/client2\n" - if response.String() != want { - t.Errorf("Clients.List returned:\n%+v\nwant:\n%+v\n", response.String(), want) + want2 := "client2 => http://localhost/clients/client2\nclient1 => http://localhost/clients/client1\n" + rstr := response.String() + if rstr != want && rstr != want2 { + t.Errorf("Clients.List returned:\n%+v\nwant:\n%+v\n", rstr, want) } } diff --git a/databag_test.go b/databag_test.go index 5ea7514..071fc4b 100644 --- a/databag_test.go +++ b/databag_test.go @@ -157,8 +157,11 @@ func TestDataBagsService_UpdateItem(t *testing.T) { func TestDataBagsService_DataBagListResultString(t *testing.T) { e := &DataBagListResult{"bag1": "http://localhost/data/bag1", "bag2": "http://localhost/data/bag2"} + // The output order is not guarenteed by the String function, check for either order want := "bag1 => http://localhost/data/bag1\nbag2 => http://localhost/data/bag2\n" - if e.String() != want { - t.Errorf("DataBagListResult.String returned:\n%+v\nwant:\n%+v\n", e.String(), want) + want2 := "bag2 => http://localhost/data/bag2\nbag1 => http://localhost/data/bag1\n" + ebag := e.String() + if ebag != want && ebag != want2 { + t.Errorf("DataBagListResult.String returned:\n%+v\nwant:\n%+v\n", ebag, want) } } diff --git a/environment_test.go b/environment_test.go index 1bb7470..ff1b37a 100644 --- a/environment_test.go +++ b/environment_test.go @@ -143,16 +143,19 @@ func TestEnvironmentsService_Put(t *testing.T) { func TestEnvironmentsService_EnvironmentListResultString(t *testing.T) { e := &EnvironmentResult{"_default": "https://api.opscode.com/organizations/org_name/environments/_default", "webserver": "https://api.opscode.com/organizations/org_name/environments/webserver"} + estr := e.String() want := "_default => https://api.opscode.com/organizations/org_name/environments/_default\nwebserver => https://api.opscode.com/organizations/org_name/environments/webserver\n" - if e.String() != want { - t.Errorf("EnvironmentResult.String returned:\n%+v\nwant:\n%+v\n", e.String(), want) + want2 := "webserver => https://api.opscode.com/organizations/org_name/environments/webserver\n_default => https://api.opscode.com/organizations/org_name/environments/_default\n" + if estr != want && estr != want2 { + t.Errorf("EnvironmentResult.String returned:\n%+v\nwant:\n%+v\n", estr, want) } } func TestEnvironmentsService_EnvironmentCreateResultString(t *testing.T) { e := &EnvironmentResult{"uri": "http://localhost:4000/environments/dev"} + estr := e.String() want := "uri => http://localhost:4000/environments/dev\n" - if e.String() != want { - t.Errorf("EnvironmentResult.String returned %+v, want %+v", e.String(), want) + if estr != want { + t.Errorf("EnvironmentResult.String returned %+v, want %+v", estr, want) } } diff --git a/role_test.go b/role_test.go index 6ce09af..cfc9e66 100644 --- a/role_test.go +++ b/role_test.go @@ -178,17 +178,19 @@ func TestRolesService_Put(t *testing.T) { func TestRolesService_RoleListResultString(t *testing.T) { r := &RoleListResult{"foo": "http://localhost:4000/roles/foo"} + rstr := r.String() want := "foo => http://localhost:4000/roles/foo\n" - if r.String() != want { - t.Errorf("RoleListResult.String returned %+v, want %+v", r.String(), want) + if rstr != want { + t.Errorf("RoleListResult.String returned %+v, want %+v", rstr, want) } } func TestRolesService_RoleCreateResultString(t *testing.T) { r := &RoleCreateResult{"uri": "http://localhost:4000/roles/webserver"} + rstr := r.String() want := "uri => http://localhost:4000/roles/webserver\n" - if r.String() != want { - t.Errorf("RoleCreateResult.String returned %+v, want %+v", r.String(), want) + if rstr != want { + t.Errorf("RoleCreateResult.String returned %+v, want %+v", rstr, want) } } From 0ac6ff386cc5318081484350bb53d068a743d486 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 7 Dec 2019 07:50:53 -0800 Subject: [PATCH 13/65] Bump the go version used --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 57a91e4..f749683 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,7 @@ workflows: jobs: update_tag: docker: - - image: circleci/golang:1.9 + - image: circleci/golang:1.13.1 working_directory: /go/src/github.com/go-chef/chefi steps: - add_ssh_keys: From 7f7d567fa48b1ecb548c5e4cc24eb31068e9507d Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 8 Dec 2019 14:09:50 -0800 Subject: [PATCH 14/65] Disable go fmt checks --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f749683..3bdde15 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,6 +46,6 @@ jobs: - run: go get github.com/ctdk/goiardi/chefcrypto - run: go get github.com/smartystreets/goconvey/convey - run: go get github.com/stretchr/testify/assert - - run: gofmt -l -d *.go && test -z $(go fmt ./....) + - run: # gofmt -l -d *.go && test -z $(go fmt ./....) - run: go vet ./... - run: go test ./... From 281ddbada11f814d4b7f1aad7ab3966621c1034e Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 8 Dec 2019 14:12:27 -0800 Subject: [PATCH 15/65] Remove the fmt check --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3bdde15..f24a672 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,6 +46,5 @@ jobs: - run: go get github.com/ctdk/goiardi/chefcrypto - run: go get github.com/smartystreets/goconvey/convey - run: go get github.com/stretchr/testify/assert - - run: # gofmt -l -d *.go && test -z $(go fmt ./....) - run: go vet ./... - run: go test ./... From 99e72ebca7632185444e84abe366c642ad3c6918 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 8 Dec 2019 20:24:11 -0800 Subject: [PATCH 16/65] Remove debugging code --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f24a672..26aea15 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,8 +40,6 @@ jobs: - 35:v45:41:ba:cf:3f:7f:d5:00:0f:11:6b:4d:c0:a1:90 - checkout # specify any bash command here prefixed with `run: ` - - run: env - - run: ls && pwd - run: go get -v ./... - run: go get github.com/ctdk/goiardi/chefcrypto - run: go get github.com/smartystreets/goconvey/convey From 115204b6f7c62401c343e7ed5efa5cf040264c92 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Wed, 25 Dec 2019 14:28:45 -0800 Subject: [PATCH 17/65] Add a cookbook to spin up a chef server Run tests of examples of using the chef server api Verify the output of the tests This PR shows how to use the go api front end and is used to verify the output from the chef api matches the go structures defined here. --- examples/README.md | 3 + examples/bin | 1 + examples/chefapi_examples/.gitignore | 21 ++ examples/chefapi_examples/Berksfile | 4 + examples/chefapi_examples/README.md | 11 + examples/chefapi_examples/TODO.md | 2 + examples/chefapi_examples/chefignore | 107 +++++++++ .../files/default/chef_objects.sh | 30 +++ .../go/src/chefapi_test/ExampleGoals.md | 34 +++ .../files/default/go/src/chefapi_test/TODO | 90 ++++++++ .../default/go/src/chefapi_test/bin/creds | 4 + .../go/src/chefapi_test/bin/organization | 13 ++ .../default/go/src/chefapi_test/bin/setup | 1 + .../src/chefapi_test/cmd/cookbook/cookbook.go | 207 +++++++++++++++++ .../go/src/chefapi_test/cmd/group/group.go | 191 ++++++++++++++++ .../cmd/organization/organization.go | 143 ++++++++++++ .../go/src/chefapi_test/cmd/user/user.go | 216 ++++++++++++++++++ .../files/default/test_book/README.md | 1 + .../default/test_book/attributes/default.rb | 1 + .../default/test_book/files/default/testfile | 1 + .../files/default/test_book/metadata.rb | 3 + .../default/test_book/recipes/default.rb | 1 + .../default/test_book/templates/test.erb | 1 + examples/chefapi_examples/kitchen.yml | 23 ++ examples/chefapi_examples/metadata.rb | 10 + .../chefapi_examples/recipes/chef_objects.rb | 64 ++++++ examples/chefapi_examples/recipes/chefapi.rb | 27 +++ examples/chefapi_examples/recipes/default.rb | 7 + examples/chefapi_examples/recipes/setup.rb | 43 ++++ examples/chefapi_examples/spec/spec_helper.rb | 2 + .../spec/unit/recipes/default_spec.rb | 22 ++ .../default/inspec/organization_spec.rb | 18 ++ examples/code | 1 + examples/{ => old}/cookbooks/cookbooks.go | 0 examples/{ => old}/cookbooks/key.pem | 0 examples/{ => old}/nodes/key.pem | 0 examples/{ => old}/nodes/nodes.go | 0 examples/{ => old}/sandboxes/key.pem | 0 examples/{ => old}/sandboxes/sandboxes.go | 0 examples/{ => old}/search/key.pem | 0 examples/{ => old}/search/search.go | 0 41 files changed, 1303 insertions(+) create mode 100644 examples/README.md create mode 120000 examples/bin create mode 100644 examples/chefapi_examples/.gitignore create mode 100644 examples/chefapi_examples/Berksfile create mode 100644 examples/chefapi_examples/README.md create mode 100644 examples/chefapi_examples/TODO.md create mode 100644 examples/chefapi_examples/chefignore create mode 100644 examples/chefapi_examples/files/default/chef_objects.sh create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/ExampleGoals.md create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/TODO create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/setup create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/group/group.go create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go create mode 100644 examples/chefapi_examples/files/default/test_book/README.md create mode 100644 examples/chefapi_examples/files/default/test_book/attributes/default.rb create mode 100644 examples/chefapi_examples/files/default/test_book/files/default/testfile create mode 100644 examples/chefapi_examples/files/default/test_book/metadata.rb create mode 100644 examples/chefapi_examples/files/default/test_book/recipes/default.rb create mode 100644 examples/chefapi_examples/files/default/test_book/templates/test.erb create mode 100644 examples/chefapi_examples/kitchen.yml create mode 100644 examples/chefapi_examples/metadata.rb create mode 100644 examples/chefapi_examples/recipes/chef_objects.rb create mode 100644 examples/chefapi_examples/recipes/chefapi.rb create mode 100644 examples/chefapi_examples/recipes/default.rb create mode 100644 examples/chefapi_examples/recipes/setup.rb create mode 100644 examples/chefapi_examples/spec/spec_helper.rb create mode 100644 examples/chefapi_examples/spec/unit/recipes/default_spec.rb create mode 100644 examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb create mode 120000 examples/code rename examples/{ => old}/cookbooks/cookbooks.go (100%) rename examples/{ => old}/cookbooks/key.pem (100%) rename examples/{ => old}/nodes/key.pem (100%) rename examples/{ => old}/nodes/nodes.go (100%) rename examples/{ => old}/sandboxes/key.pem (100%) rename examples/{ => old}/sandboxes/sandboxes.go (100%) rename examples/{ => old}/search/key.pem (100%) rename examples/{ => old}/search/search.go (100%) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..29477dd --- /dev/null +++ b/examples/README.md @@ -0,0 +1,3 @@ +# Examples for using the api +## Coded samples +## Cookbook and kitchen testing diff --git a/examples/bin b/examples/bin new file mode 120000 index 0000000..a8a2909 --- /dev/null +++ b/examples/bin @@ -0,0 +1 @@ +./chefapi_examples/files/default/go/src/chefapi_test/bin \ No newline at end of file diff --git a/examples/chefapi_examples/.gitignore b/examples/chefapi_examples/.gitignore new file mode 100644 index 0000000..febee30 --- /dev/null +++ b/examples/chefapi_examples/.gitignore @@ -0,0 +1,21 @@ +.vagrant +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ + +# Bundler +Gemfile.lock +bin/* +.bundle/* + +# test kitchen +.kitchen/ +.kitchen.local.yml + +# Chef +Berksfile.lock +.zero-knife.rb +Policyfile.lock.json diff --git a/examples/chefapi_examples/Berksfile b/examples/chefapi_examples/Berksfile new file mode 100644 index 0000000..2e27c49 --- /dev/null +++ b/examples/chefapi_examples/Berksfile @@ -0,0 +1,4 @@ +source 'https://supermarket.chef.io' +source :chef_server + +metadata diff --git a/examples/chefapi_examples/README.md b/examples/chefapi_examples/README.md new file mode 100644 index 0000000..5a7091f --- /dev/null +++ b/examples/chefapi_examples/README.md @@ -0,0 +1,11 @@ +# chefapi_examples + +Create a chef server and tests to run integration tests of the go-chef/chef api. + +Expected functions. + +* Spin up an instance of the chef server +* Configure enough objects to illustrate the tests +* Use inspec to run test and verify the results + +# Test Procedure diff --git a/examples/chefapi_examples/TODO.md b/examples/chefapi_examples/TODO.md new file mode 100644 index 0000000..ac59edc --- /dev/null +++ b/examples/chefapi_examples/TODO.md @@ -0,0 +1,2 @@ +* Allow for testing the local go-chef/chef version +* Flag for ssl verify diff --git a/examples/chefapi_examples/chefignore b/examples/chefapi_examples/chefignore new file mode 100644 index 0000000..38e7379 --- /dev/null +++ b/examples/chefapi_examples/chefignore @@ -0,0 +1,107 @@ +# Put files/directories that should be ignored in this file when uploading +# to a chef-server or supermarket. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +examples/* +Guardfile +Procfile +.kitchen* +.rubocop.yml +spec/* +Rakefile +.travis.yml +.foodcritic +.codeclimate.yml + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +Berksfile +Berksfile.lock +cookbooks/* +tmp + +# Policyfile # +############## +Policyfile.rb +Policyfile.lock.json + +# Cookbooks # +############# +CONTRIBUTING* +CHANGELOG* +TESTING* +MAINTAINERS.toml + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile diff --git a/examples/chefapi_examples/files/default/chef_objects.sh b/examples/chefapi_examples/files/default/chef_objects.sh new file mode 100644 index 0000000..e640885 --- /dev/null +++ b/examples/chefapi_examples/files/default/chef_objects.sh @@ -0,0 +1,30 @@ +#!/bin/bash -x + +# Add two users +chef-server-ctl user-create user1 user1 mid last user1.last@nordstrom.com dummuy1pass +chef-server-ctl user-create user2 user2 mid last user2.last@nordstrom.com dummuy1pass + +# Add a user to an org +./chef-server-ctl org-user-add ORG_NAME USER_NAME +# Add an admin user to an org +./chef-server-ctl org-user-add ORG_NAME USER_NAME -a + +# define the current node to the chef server +chef-client + +# Add a cookbook +knife cookbook upload + +# Add a role + +# Add the user to a group + +# Add an environment + +# Add a data bag + +# Add a chef vault item + +# Add a node and set the run list + +# tag a node diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/ExampleGoals.md b/examples/chefapi_examples/files/default/go/src/chefapi_test/ExampleGoals.md new file mode 100644 index 0000000..b86ccf8 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/ExampleGoals.md @@ -0,0 +1,34 @@ +# Chef API testing + +For each class of chef api object class (organizations, users, cookbooks, etc and each rest end point) +* Exercise the defined functions - mostly crud +* Define input structs for the calls to the api +* Create an example of the code to call each function +* Define the expected output in a struct +* Verify the output structs are populated as expected + +|Name| New | Function tests | input structs | calls | out struct | verify out | +acl +association_request * +authenticate_user * +client +containers * +cookbook +cookbook_download +databag +environment +group +license * +node +organization +policy * +policy_group * +principal +role +run_list +sandbox +status * +search +universe * +updated_since * +user diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/TODO b/examples/chefapi_examples/files/default/go/src/chefapi_test/TODO new file mode 100644 index 0000000..933ce06 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/TODO @@ -0,0 +1,90 @@ + +Organization + create 1 + create 2 + list + get 1 + update + delete + +ACL + get + update + +Client + create 2x + list + get + update * + delete + listkeys + getkey + +Cookbooks + List + List with version limits + GetVersion + List all recipes + GetAvailableVersion + Get specific version + Delete cookbook version + Add cookbook Version + Download cookbook files => create a cookbook on disk + +Databag + Create 2x + List + Delete + ListItems + CreateItem 2x + DeleteItem + GetItem + UpdateItem + +Environment + Create 2x + Get + Put ? + List + Delete + ListCookbooks + +Node + List + Get + Post + Put + Delete + +Organizations + List + Get + Create + Delete + Update + +Principal + Get + +Role + List + Create + Delete + Get + Put + +Sandbox + Post + Put + +Search + Do + PartialExec + Indexes + +Users + Create + List + Get + Update + Delete diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds new file mode 100644 index 0000000..0ebf4a2 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds @@ -0,0 +1,4 @@ +CHEFUSER='pivotal' +KEYFILE='/etc/opscode/pivotal.pem' +CHEFGLOBALURL='https://localhost' +CHEFORGURL='https://localhost' diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization new file mode 100755 index 0000000..259838a --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization @@ -0,0 +1,13 @@ +#!/bin/bash + +# Organization testing + +# https://forfuncsake.github.io/post/2017/08/trust-extra-ca-cert-in-go-app/ +# TODO: Trust a self signed chef server cert + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds + +go run ${BASE}/../cmd/organization/organization.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/setup b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/setup new file mode 100644 index 0000000..65e8ba4 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/setup @@ -0,0 +1 @@ +export GOPATH=/go diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go new file mode 100644 index 0000000..dc5fce3 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go @@ -0,0 +1,207 @@ +// +// Test the go-chef/chef chef server api /organizations/*/cookbooks endpoints against a live chef server +// +package main + +import ( + "fmt" + "io/ioutil" + "os" + + chef "github.com/go-chef/chef" + //chef "github.com/MarkGibbons/chefapi" +) + + +// main Exercise the chef server api +func main() { + // Pass in the database and chef-server api credentials. + usr1 := "usr1" + usr2 := "usr2" + user := os.Args[1] + keyfile := os.Args[2] + chefurl := os.Args[3] + + // Create a client for user access + client := buildClient(user, keyfile, chefurl) + + // Cookbooks + fmt.Println("") + fmt.Println("Starting cookbooks") + cookbookList := listCookbooks(client) + fmt.Println(cookbookList)) + + fmt.Println("") + fmt.Println("Added usr1") + userResult := createUser(client, chef.User{ UserName: usr1, DisplayName: "Show Me", Email: "user1@domain.io", FirstName: "user1", FullName: "All the Name", LastName: "fullname", MiddleName: "J", Password: "Logn12ComplexPwd#" }) + fmt.Println(userResult) + + fmt.Println("") + fmt.Println("Filter users email=user1") + userList = listUsers(client, "email=user1@domain.io") // map[string]string a1px:https://chefp01.nordstrom.net/users/a1px + fmt.Println(userList) + + fmt.Println("") + fmt.Println("Verbose users") + userVerboseOut := listUsersVerbose(client) // []UserVerbose + fmt.Printf("Verbose Out %v\n", userVerboseOut) + + err := client.AuthenticateUser.Authenticate(chef.AuthenticateUser{ UserName: usr1, Password: "Logn12ComplexPwd#" }) + fmt.Println("") + fmt.Println("") + fmt.Println("Error returned from authenticate: ", err) + + fmt.Println("") + fmt.Println("Added usr1 again") + userResult = createUser(client, chef.User{ UserName: usr1, Email: "user1@domain.io", FirstName: "user1", LastName: "fullname", DisplayName: "User1 Fullname", Password: "mary" }) + fmt.Println(userResult) + + fmt.Println("") + fmt.Println("Added usr2") + userResult = createUser(client, chef.User{ UserName: usr2, Email: "user2@domain.io", FirstName: "User2", LastName: "Fullname", DisplayName: "User2 Fullname", ExternalAuthenticationUid: "mary" }) + fmt.Println(userResult) + + fmt.Println("") + fmt.Println("Filter users external_authentication_uid=mary") + userList = listUsers(client, "external_authentication_uid=mary") + fmt.Println(userList) + + fmt.Println("") + fmt.Println("Original usr1") + userout := getUser(client, usr1) + fmt.Println(userout) + + fmt.Println("") + fmt.Println("Original pivotal") + userout = getUser(client, "pivotal") + fmt.Println(userout) + + fmt.Println("") + fmt.Println("After adding usr1 and usr2") + userList = listUsers(client) + fmt.Println(userList) + + fmt.Println("") + fmt.Println("After updating usr1") + userbody := chef.User{ FullName: "usr1new" } + fmt.Println("User request", userbody) + userresult := updateUser(client, "usr1", userbody) + fmt.Println(userresult) + + fmt.Println("") + fmt.Println("Get usr1") + userout = getUser(client, usr1) + fmt.Println(userout) + + fmt.Println("") + fmt.Println("Delete usr2") + userd, userErr := deleteUser(client, usr2) + fmt.Println(userErr) + fmt.Println("delete data", userd) + + fmt.Println("") + fmt.Println("Delete usr1") + userd, userErr = deleteUser(client, usr1) + fmt.Println(userErr) + fmt.Println("delete data", userd) + + fmt.Println("") + fmt.Println("list after deleting users") + userList = listUsers(client) + fmt.Println(userList) + +} + +// buildClient creates a connection to a chef server using the chef api. +func buildClient(user string, keyfile string, baseurl string) *chef.Client { + key := clientKey(keyfile) + client, err := chef.NewClient(&chef.Config{ + Name: user, + Key: string(key), + BaseURL: baseurl, + // goiardi is on port 4545 by default, chef-zero is 8889, chef-server is on 443 + }) + if err != nil { + fmt.Println("Issue setting up client:", err) + os.Exit(1) + } + return client +} + +// clientKey reads the pem file containing the credentials needed to use the chef client. +func clientKey(filepath string) string { + key, err := ioutil.ReadFile(filepath) + if err != nil { + fmt.Println("Couldn't read key.pem:", err) + os.Exit(1) + } + return string(key) +} + +// createOrganization uses the chef server api to create a single organization +func createOrganization(client *chef.Client, org chef.Organization) chef.OrganizationResult { + orgResult, err := client.Organizations.Create(org) + if err != nil { + fmt.Println("Issue creating org:", err) + } + return orgResult +} + +// deleteOrganization uses the chef server api to delete a single organization +func deleteOrganization(client *chef.Client, name string) error { + err := client.Organizations.Delete(name) + if err != nil { + fmt.Println("Issue deleting org:", err) + } + return err +} + +// createUser uses the chef server api to create a single organization +func createUser(client *chef.Client, user chef.User) chef.UserResult { + usrResult, err := client.Users.Create(user) + if err != nil { + fmt.Println("Issue creating user:", err) + } + return usrResult +} + +// deleteUser uses the chef server api to delete a single organization +func deleteUser(client *chef.Client, name string) (data chef.UserResult, err error) { + err = client.Users.Delete(name) + if err != nil { + fmt.Println("Issue deleting org:", err) + } + return +} + +// getUser uses the chef server api to get information for a single user +func getUser(client *chef.Client, name string) chef.User { + userList, err := client.Users.Get(name) + if err != nil { + fmt.Println("Issue listing user", err) + } + return userList +} + +// listCookbooks uses the chef server api to list all cookbooks +func listCookbooks(client *chef.Client, filters ...string) map[string]string { + var filter string + if len(filters) > 0 { + filter = filters[0] + } + userList, err := client.Users.List(filter) + if err != nil { + fmt.Println("Issue listing users:", err) + } + return userList +} + +// listUsersVerbose uses the chef server api to list all users and return verbose output +func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseItem { + userList, err := client.Users.ListVerbose() + fmt.Println("VERBOSE LIST", userList) + if err != nil { + fmt.Println("Issue listing verbose users:", err) + } + return userList +} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/group/group.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/group/group.go new file mode 100644 index 0000000..1263b0f --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/group/group.go @@ -0,0 +1,191 @@ +// +// Test the go-chef/chef chef server api /organizations endpoints against a live server +// +package main + +import ( + "fmt" + "io/ioutil" + "os" + "strconv" + + chef "github.com/go-chef/chef" +) + + +// main Exercise the chef server api +func main() { + // Pass in the database and chef-server api credentials. + org1 := "org1" + org2 := "org2" + user := os.Args[1] + keyfile := os.Args[2] + chefurl := os.Args[3] + skipssl, err := strconv.ParseBool(os.Args[4]) + if err != nil { + skipssl = true + } + + // Create a client for access + client := buildClient(user, keyfile, chefurl, skipssl) + + // Organization tests + orgList := listOrganizations(client) + fmt.Println("List initial organizations", orglistorganization.go) + + orgResult := createOrganization(client, chef.Organization{Name: "org1", FullName: "organization1"}) + fmt.Println("Added org1", orgResult) + + orgResult = createOrganization(client, chef.Organization{Name: "org1", FullName: "organization1"}) + fmt.Println("Added org1 again", orgResult) + + orgResult = createOrganization(client, chef.Organization{Name: "org2", FullName: "organization2"}) + fmt.Println("Added org2", orgResult) + + orgout := getOrganization(client, org1) + fmt.Println("Get org1", orgout) + + orgList = listOrganizations(client) + fmt.Println("List organizations After adding org1 and org2", orgList) + + orgresult := updateOrganization(client, chef.Organization{Name: "org1", FullName: "new_organization1"}) + fmt.Println("Update org1", orgresult) + + orgout = getOrganization(client, org1) + fmt.Println("Get org1 after update", orgout) + + orgErr := deleteOrganization(client, org2) + fmt.Println("Delete org2", orgErr) + + orgList = listOrganizations(client) + fmt.Println("List organizations after deleting org2", orgList) + + // Clean up + orgErr = deleteOrganization(client, org1) + fmt.Println("Result from deleting org1", orgErr) + + orgList = listOrganizations(client) + fmt.Println("List organizations after cleanup", orgList) + +} + +// buildClient creates a connection to a chef server using the chef api. +func buildClient(user string, keyfile string, baseurl string, skipssl bool) *chef.Client { + key := clientKey(keyfile) + client, err := chef.NewClient(&chef.Config{ + Name: user, + Key: string(key), + BaseURL: baseurl, + SkipSSL: skipssl, + // goiardi is on port 4545 by default, chef-zero is 8889, chef-server is on 443 + }) + if err != nil { + fmt.Println("Issue setting up client:", err) + os.Exit(1) + } + return client +} + +// clientKey reads the pem file containing the credentials needed to use the chef client. +func clientKey(filepath string) string { + key, err := ioutil.ReadFile(filepath) + if err != nil { + fmt.Println("Couldn't read key.pem:", err) + os.Exit(1) + } + return string(key) +} + +// createOrganization uses the chef server api to create a single organization +func createOrganization(client *chef.Client, org chef.Organization) chef.OrganizationResult { + orgResult, err := client.Organizations.Create(org) + if err != nil { + fmt.Println("Issue creating org:", err) + } + return orgResult +} + +// deleteOrganization uses the chef server api to delete a single organization +func deleteOrganization(client *chef.Client, name string) error { + err := client.Organizations.Delete(name) + if err != nil { + fmt.Println("Issue deleting org:", err) + } + return err +} + +// getOrganization uses the chef server api to get information for a single organization +func getOrganization(client *chef.Client, name string) chef.Organization { + // todo: everything + orgList, err := client.Organizations.Get(name) + if err != nil { + fmt.Println("Issue listing orgs:", err) + } + return orgList +} + +// listOrganizations uses the chef server api to list all organizations +func listOrganizations(client *chef.Client) map[string]string { + orgList, err := client.Organizations.List() + if err != nil { + fmt.Println("Issue listing orgs:", err) + } + return orgList +} + +// updateOrganization uses the chef server api to update information for a single organization +func updateOrganization(client *chef.Client, org chef.Organization) chef.Organization { + org_update, err := client.Organizations.Update(org) + if err != nil { + fmt.Println("Issue updating org:", err) + } + return org_update +} + +/* +// orgGroups gets a list of groups, from the chef server, belonging to an organization. +func orgGroups(client *chef.Client, org string) map[string]string { + groupList, err := client.Groups.List() + if err != nil { + fmt.Println("Issue listing groups:", err) + panic(err.Error()) // proper error handling instead of panic in your app + } + return groupList +} + +// getGroup gets group information from the chef server. The +// members of the group and nested groups are retrieved. +func getGroup(client *chef.Client, group string) chef.Group { + groupInfo, err := client.Groups.Get(group) + if err != nil { + fmt.Println("Issue getting: "+group, err) + panic(err.Error()) // proper error handling instead of panic in your app + } + return groupInfo +} + +// getMember gets the information associated with a particular user account. +func getMember(client *chef.Client, member string) chef.User { + memberInfo, err := client.Users.Get(member) + if err != nil { + fmt.Println("Issue getting: "+member, err) + panic(err.Error()) // proper error handling instead of panic in your app + } + return memberInfo +} + +// usersFromGroups gets the nested groups. getGroupMembers and userFromGroups +// call each other in a recursive fashion to expand the nested groups +func usersFromGroups(client *chef.Client, groups []string) []string { + var members []string + for _, group := range groups { + groupInfo, err := client.Groups.Get(group) + if err != nil { + fmt.Println("Issue with regex", err) + panic(err.Error()) // proper error handling instead of panic in your app + } + members = getGroupMembers(client, groupInfo) + } + return members +} +*/ diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go new file mode 100644 index 0000000..cc6b7fc --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go @@ -0,0 +1,143 @@ +// +// Test the go-chef/chef chef server api /organizations endpoints against a live server +// +package main + +import ( + "fmt" + "io/ioutil" + "os" + "strconv" + + chef "github.com/go-chef/chef" +) + + +// main Exercise the chef server api +func main() { + // Pass in the database and chef-server api credentials. + org1 := "org1" + org2 := "org2" + user := os.Args[1] + keyfile := os.Args[2] + chefurl := os.Args[3] + skipssl, err := strconv.ParseBool(os.Args[4]) + if err != nil { + skipssl = true + } + + // Create a client for access + client := buildClient(user, keyfile, chefurl, skipssl) + + // Organization tests + orgList := listOrganizations(client) + fmt.Println("List initial organizations", orgList) + + orgResult := createOrganization(client, chef.Organization{Name: "org1", FullName: "organization1"}) + fmt.Println("Added org1", orgResult) + + orgResult = createOrganization(client, chef.Organization{Name: "org1", FullName: "organization1"}) + fmt.Println("Added org1 again", orgResult) + + orgResult = createOrganization(client, chef.Organization{Name: "org2", FullName: "organization2"}) + fmt.Println("Added org2", orgResult) + + orgout := getOrganization(client, org1) + fmt.Println("Get org1", orgout) + + orgList = listOrganizations(client) + fmt.Println("List organizations After adding org1 and org2", orgList) + + orgresult := updateOrganization(client, chef.Organization{Name: "org1", FullName: "new_organization1"}) + fmt.Println("Update org1", orgresult) + + orgout = getOrganization(client, org1) + fmt.Println("Get org1 after update", orgout) + + orgErr := deleteOrganization(client, org2) + fmt.Println("Delete org2", orgErr) + + orgList = listOrganizations(client) + fmt.Println("List organizations after deleting org2", orgList) + + // Clean up + orgErr = deleteOrganization(client, org1) + fmt.Println("Result from deleting org1", orgErr) + + orgList = listOrganizations(client) + fmt.Println("List organizations after cleanup", orgList) + +} + +// buildClient creates a connection to a chef server using the chef api. +func buildClient(user string, keyfile string, baseurl string, skipssl bool) *chef.Client { + key := clientKey(keyfile) + client, err := chef.NewClient(&chef.Config{ + Name: user, + Key: string(key), + BaseURL: baseurl, + SkipSSL: skipssl, + // goiardi is on port 4545 by default, chef-zero is 8889, chef-server is on 443 + }) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue setting up client:", err) + os.Exit(1) + } + return client +} + +// clientKey reads the pem file containing the credentials needed to use the chef client. +func clientKey(filepath string) string { + key, err := ioutil.ReadFile(filepath) + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't read key.pem:", err) + os.Exit(1) + } + return string(key) +} + +// createOrganization uses the chef server api to create a single organization +func createOrganization(client *chef.Client, org chef.Organization) chef.OrganizationResult { + orgResult, err := client.Organizations.Create(org) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue creating org:", org, err) + } + return orgResult +} + +// deleteOrganization uses the chef server api to delete a single organization +func deleteOrganization(client *chef.Client, name string) error { + err := client.Organizations.Delete(name) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting org:", name, err) + } + return err +} + +// getOrganization uses the chef server api to get information for a single organization +func getOrganization(client *chef.Client, name string) chef.Organization { + // todo: everything + orgList, err := client.Organizations.Get(name) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue listing org:", name, err) + } + return orgList +} + +// listOrganizations uses the chef server api to list all organizations +func listOrganizations(client *chef.Client) map[string]string { + orgList, err := client.Organizations.List() + if err != nil { + fmt.Fprintln(os.Stderr, "Issue listing orgs:", err) + } + return orgList +} + +// updateOrganization uses the chef server api to update information for a single organization +func updateOrganization(client *chef.Client, org chef.Organization) chef.Organization { + org_update, err := client.Organizations.Update(org) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue updating org:", org, err) + } + return org_update +} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go new file mode 100644 index 0000000..c8ca1f8 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go @@ -0,0 +1,216 @@ +// +// Test the go-chef/chef chef server api /users endpoints against a live chef server +// +package main + +import ( + "fmt" + "io/ioutil" + "os" + + // "github.com/go-chef/chef" + chef "github.com/MarkGibbons/chefapi" +) + + +// main Exercise the chef server api +func main() { + // Pass in the database and chef-server api credentials. + usr1 := "usr1" + usr2 := "usr2" + user := os.Args[1] + keyfile := os.Args[2] + chefurl := os.Args[3] + + // Create a client for user access + client := buildClient(user, keyfile, chefurl) + + // Users + fmt.Println("") + fmt.Println("Starting users") + userList := listUsers(client) // map[string]string a1px:https://chefp01.nordstrom.net/users/a1px + fmt.Println(userList) + + fmt.Println("") + fmt.Println("Added usr1") + userResult := createUser(client, chef.User{ UserName: usr1, DisplayName: "Show Me", Email: "user1@domain.io", FirstName: "user1", FullName: "All the Name", LastName: "fullname", MiddleName: "J", Password: "Logn12ComplexPwd#" }) + fmt.Println(userResult) + + fmt.Println("") + fmt.Println("Filter users email=user1") + userList = listUsers(client, "email=user1@domain.io") // map[string]string a1px:https://chefp01.nordstrom.net/users/a1px + fmt.Println(userList) + + fmt.Println("") + fmt.Println("Verbose users") + userVerboseOut := listUsersVerbose(client) // []UserVerbose + fmt.Printf("Verbose Out %v\n", userVerboseOut) + + err := client.AuthenticateUser.Authenticate(chef.AuthenticateUser{ UserName: usr1, Password: "Logn12ComplexPwd#" }) + fmt.Println("") + fmt.Println("") + fmt.Println("Error returned from authenticate: ", err) + + fmt.Println("") + fmt.Println("Added usr1 again") + userResult = createUser(client, chef.User{ UserName: usr1, Email: "user1@domain.io", FirstName: "user1", LastName: "fullname", DisplayName: "User1 Fullname", Password: "mary" }) + fmt.Println(userResult) + + fmt.Println("") + fmt.Println("Added usr2") + userResult = createUser(client, chef.User{ UserName: usr2, Email: "user2@domain.io", FirstName: "User2", LastName: "Fullname", DisplayName: "User2 Fullname", ExternalAuthenticationUid: "mary" }) + fmt.Println(userResult) + + fmt.Println("") + fmt.Println("Filter users external_authentication_uid=mary") + userList = listUsers(client, "external_authentication_uid=mary") + fmt.Println(userList) + + fmt.Println("") + fmt.Println("Original usr1") + userout := getUser(client, usr1) + fmt.Println(userout) + + fmt.Println("") + fmt.Println("Original pivotal") + userout = getUser(client, "pivotal") + fmt.Println(userout) + + fmt.Println("") + fmt.Println("After adding usr1 and usr2") + userList = listUsers(client) + fmt.Println(userList) + + fmt.Println("") + fmt.Println("After updating usr1") + userbody := chef.User{ FullName: "usr1new" } + fmt.Println("User request", userbody) + userresult := updateUser(client, "usr1", userbody) + fmt.Println(userresult) + + fmt.Println("") + fmt.Println("Get usr1") + userout = getUser(client, usr1) + fmt.Println(userout) + + fmt.Println("") + fmt.Println("Delete usr2") + userd, userErr := deleteUser(client, usr2) + fmt.Println(userErr) + fmt.Println("delete data", userd) + + fmt.Println("") + fmt.Println("Delete usr1") + userd, userErr = deleteUser(client, usr1) + fmt.Println(userErr) + fmt.Println("delete data", userd) + + fmt.Println("") + fmt.Println("list after deleting users") + userList = listUsers(client) + fmt.Println(userList) + +} + +// buildClient creates a connection to a chef server using the chef api. +func buildClient(user string, keyfile string, baseurl string) *chef.Client { + key := clientKey(keyfile) + client, err := chef.NewClient(&chef.Config{ + Name: user, + Key: string(key), + BaseURL: baseurl, + // goiardi is on port 4545 by default, chef-zero is 8889, chef-server is on 443 + }) + if err != nil { + fmt.Println("Issue setting up client:", err) + os.Exit(1) + } + return client +} + +// clientKey reads the pem file containing the credentials needed to use the chef client. +func clientKey(filepath string) string { + key, err := ioutil.ReadFile(filepath) + if err != nil { + fmt.Println("Couldn't read key.pem:", err) + os.Exit(1) + } + return string(key) +} + +// createOrganization uses the chef server api to create a single organization +func createOrganization(client *chef.Client, org chef.Organization) chef.OrganizationResult { + orgResult, err := client.Organizations.Create(org) + if err != nil { + fmt.Println("Issue creating org:", err) + } + return orgResult +} + +// deleteOrganization uses the chef server api to delete a single organization +func deleteOrganization(client *chef.Client, name string) error { + err := client.Organizations.Delete(name) + if err != nil { + fmt.Println("Issue deleting org:", err) + } + return err +} + +// createUser uses the chef server api to create a single organization +func createUser(client *chef.Client, user chef.User) chef.UserResult { + usrResult, err := client.Users.Create(user) + if err != nil { + fmt.Println("Issue creating user:", err) + } + return usrResult +} + +// deleteUser uses the chef server api to delete a single organization +func deleteUser(client *chef.Client, name string) (data chef.UserResult, err error) { + err = client.Users.Delete(name) + if err != nil { + fmt.Println("Issue deleting org:", err) + } + return +} + +// getUser uses the chef server api to get information for a single user +func getUser(client *chef.Client, name string) chef.User { + userList, err := client.Users.Get(name) + if err != nil { + fmt.Println("Issue listing user", err) + } + return userList +} + +// listUsers uses the chef server api to list all users +func listUsers(client *chef.Client, filters ...string) map[string]string { + var filter string + if len(filters) > 0 { + filter = filters[0] + } + userList, err := client.Users.List(filter) + if err != nil { + fmt.Println("Issue listing users:", err) + } + return userList +} + +// listUsersVerbose uses the chef server api to list all users and return verbose output +func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseItem { + userList, err := client.Users.ListVerbose() + fmt.Println("VERBOSE LIST", userList) + if err != nil { + fmt.Println("Issue listing verbose users:", err) + } + return userList +} + +// updateUser uses the chef server api to update information for a single user +func updateUser(client *chef.Client, username string, user chef.User) chef.User { + user_update, err := client.Users.Update(username, user) + if err != nil { + fmt.Println("Issue updating user:", err) + } + return user_update +} diff --git a/examples/chefapi_examples/files/default/test_book/README.md b/examples/chefapi_examples/files/default/test_book/README.md new file mode 100644 index 0000000..59d79a7 --- /dev/null +++ b/examples/chefapi_examples/files/default/test_book/README.md @@ -0,0 +1 @@ +An example test cookbook diff --git a/examples/chefapi_examples/files/default/test_book/attributes/default.rb b/examples/chefapi_examples/files/default/test_book/attributes/default.rb new file mode 100644 index 0000000..2b902d4 --- /dev/null +++ b/examples/chefapi_examples/files/default/test_book/attributes/default.rb @@ -0,0 +1 @@ +# default attributes file diff --git a/examples/chefapi_examples/files/default/test_book/files/default/testfile b/examples/chefapi_examples/files/default/test_book/files/default/testfile new file mode 100644 index 0000000..9c1732b --- /dev/null +++ b/examples/chefapi_examples/files/default/test_book/files/default/testfile @@ -0,0 +1 @@ +# sample test file diff --git a/examples/chefapi_examples/files/default/test_book/metadata.rb b/examples/chefapi_examples/files/default/test_book/metadata.rb new file mode 100644 index 0000000..792c6fd --- /dev/null +++ b/examples/chefapi_examples/files/default/test_book/metadata.rb @@ -0,0 +1,3 @@ +name 'testbook' +description 'A test cookbook that does nothing' +version '0.1.0' diff --git a/examples/chefapi_examples/files/default/test_book/recipes/default.rb b/examples/chefapi_examples/files/default/test_book/recipes/default.rb new file mode 100644 index 0000000..c452f47 --- /dev/null +++ b/examples/chefapi_examples/files/default/test_book/recipes/default.rb @@ -0,0 +1 @@ +# default recipe diff --git a/examples/chefapi_examples/files/default/test_book/templates/test.erb b/examples/chefapi_examples/files/default/test_book/templates/test.erb new file mode 100644 index 0000000..6891ffd --- /dev/null +++ b/examples/chefapi_examples/files/default/test_book/templates/test.erb @@ -0,0 +1 @@ +# Sample template diff --git a/examples/chefapi_examples/kitchen.yml b/examples/chefapi_examples/kitchen.yml new file mode 100644 index 0000000..502da98 --- /dev/null +++ b/examples/chefapi_examples/kitchen.yml @@ -0,0 +1,23 @@ +--- +driver: + name: vagrant + +provisioner: + name: chef_zero + +verifier: + name: inspec + +platforms: + - name: ubuntu-18.04 + +suites: + - name: default + run_list: + - recipe[chefapi_examples::setup] + - recipe[chefapi_examples::default] + - recipe[chefapi_examples::chef_objects] + - recipe[chefapi_examples::chefapi] + attributes: + chef-server: + accept_license: true diff --git a/examples/chefapi_examples/metadata.rb b/examples/chefapi_examples/metadata.rb new file mode 100644 index 0000000..0cdb1c1 --- /dev/null +++ b/examples/chefapi_examples/metadata.rb @@ -0,0 +1,10 @@ +name 'chefapi_examples' +maintainer 'go-chef/chef project maintainers' +maintainer_email 'you@example.com' +license 'All Rights Reserved' +description 'Test the chef server api' +version '0.1.0' +chef_version '>= 13' +depends 'chef-server' +depends 'fileutils' +depends 'line' diff --git a/examples/chefapi_examples/recipes/chef_objects.rb b/examples/chefapi_examples/recipes/chef_objects.rb new file mode 100644 index 0000000..7bd0816 --- /dev/null +++ b/examples/chefapi_examples/recipes/chef_objects.rb @@ -0,0 +1,64 @@ +# Add chef objects to the server for testing + +execute 'Set the host name' do + command 'hostname testhost' +end + +append_if_no_line 'add hostname to /etc/hosts' do + line '127.0.01 testhost' + path '/etc/hosts' +end + +# Create an organization + +execute 'create test organization' do + command '/opt/opscode/bin/chef-server-ctl org-create test test_org' + not_if '/opt/opscode/bin/chef-server-ctl org-list |grep test' +end + +execute 'get the ssl certificate for the chef server' do + command 'knife ssl fetch' + not_if { File.exist? '/root/.chef/trusted_certs/testhost' } +end + +# Register this node with the server +directory '/etc/chef' +file '/etc/chef/client.rb' do + content ' +chef_server_url "https://testhost/organizations/test" +client_fork true +file_backup_path "/var/chef/backup" +file_cache_path "/var/chef/cache" +log_location "/var/log/chef/client.log" +nodename "testhost" +validation_client_name "pivotal" +validation_key "/etc/opscode/pivotal.pem" +trusted_certs_dir "/root/.chef/trusted_certs" +ohai.disabled_plugins = [:C,:Cloud,:Rackspace,:Eucalyptus,:Command,:DMI,:Erlang,:Groovy,:IpScopes,:Java,:Lua,:Mono,:NetworkListeners,:Passwd,:Perl,:PHP,:Python] +' +end + +directory '/fixtures/bin' do + recursive true +end + +directory '/fixtures/chef/cookbooks' do + recursive true +end + +cookbook_file '/fixtures/bin/chef_objects.sh' do + source 'chef_objects.sh' + mode '0755' +end + +remote_directory '/fixtures/chef/cookbooks/test_book' do + source 'test_book' +end + +directory '/var/log/chef' do + recursive true +end + +directory '/var/chef' do + recursive true +end diff --git a/examples/chefapi_examples/recipes/chefapi.rb b/examples/chefapi_examples/recipes/chefapi.rb new file mode 100644 index 0000000..2adece0 --- /dev/null +++ b/examples/chefapi_examples/recipes/chefapi.rb @@ -0,0 +1,27 @@ +# recipe chef_tester::chefapi +# + +package 'git' + +package 'golang' + +directory '/go/src/github.com/go-chef' do + recursive true +end + +# TODO: allow for testing a branch or the version of the api this cookbook is embedded in +git '/go/src/github.com/go-chef/chef' do + repository 'https://github.com/go-chef/chef.git' +end + +remote_directory 'local_go' do + files_backup false + path '/go' + purge false + recursive true + source 'go' +end + +fileutils '/go/src/chefapi_test/bin' do + file_mode ['+x'] +end diff --git a/examples/chefapi_examples/recipes/default.rb b/examples/chefapi_examples/recipes/default.rb new file mode 100644 index 0000000..3504234 --- /dev/null +++ b/examples/chefapi_examples/recipes/default.rb @@ -0,0 +1,7 @@ +# +# Cookbook:: chefapi_examples +# Recipe:: default +# +# Copyright:: 2019, The Authors, All Rights Reserved. + +include_recipe 'chef-server' diff --git a/examples/chefapi_examples/recipes/setup.rb b/examples/chefapi_examples/recipes/setup.rb new file mode 100644 index 0000000..f3d61c1 --- /dev/null +++ b/examples/chefapi_examples/recipes/setup.rb @@ -0,0 +1,43 @@ +directory '/etc/chef/accepted_licenses' do + recursive true +end + +file '/etc/chef/accepted_licenses/chef_infra_client' do + content "--- +id: infra-client +name: Chef Infra Client +date_accepted: '2019-10-20T14:46:27+00:00' +accepting_product: infra-server +accepting_product_version: 0.6.0 +user: vagrant +file_format: 1" +end + +file '/etc/chef/accepted_licenses/chef_infra_server' do + content "--- + id: infra-server + name: Chef Infra Server + date_accepted: '2019-10-20T14:46:27+00:00' + accepting_product: infra-server + accepting_product_version: 0.6.0 + user: vagrant + file_format: 1" +end + +file '/etc/chef/accepted_licenses/inspec' do + content "--- +id: inspec +name: Chef InSpec +date_accepted: '2019-10-20T14:46:27+00:00' +accepting_product: infra-server +accepting_product_version: 0.6.0 +user: vagrant +file_format: 1" +end + +apt_update 'update' + +execute 'apt upgrade' do + command 'DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade' + ignore_failure true +end diff --git a/examples/chefapi_examples/spec/spec_helper.rb b/examples/chefapi_examples/spec/spec_helper.rb new file mode 100644 index 0000000..1dd5126 --- /dev/null +++ b/examples/chefapi_examples/spec/spec_helper.rb @@ -0,0 +1,2 @@ +require 'chefspec' +require 'chefspec/berkshelf' diff --git a/examples/chefapi_examples/spec/unit/recipes/default_spec.rb b/examples/chefapi_examples/spec/unit/recipes/default_spec.rb new file mode 100644 index 0000000..803ec0c --- /dev/null +++ b/examples/chefapi_examples/spec/unit/recipes/default_spec.rb @@ -0,0 +1,22 @@ +# +# Cookbook:: chefapi_examples +# Spec:: default +# +# Copyright:: 2017, The Authors, All Rights Reserved. + +require 'spec_helper' + +describe 'chefapi_examples::default' do + context 'When all attributes are default, on an Ubuntu 16.04' do + let(:chef_run) do + # for a complete list of available platforms and versions see: + # https://github.com/customink/fauxhai/blob/master/PLATFORMS.md + runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04') + runner.converge(described_recipe) + end + + it 'converges successfully' do + expect { chef_run }.to_not raise_error + end + end +end diff --git a/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb new file mode 100644 index 0000000..8b39e15 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb @@ -0,0 +1,18 @@ +# Inspec tests for the organization chef api go module +# + +describe command('/go/src/chefapi_test/bin/organization') do + its('stdout') { should match(/^List initial organizations map\[test.*test\]$/) } + its('stdout') { should match(/^Added org1 {org1-validator -----BEGIN RSA PRIVATE KEY-----/) } + its('stdout') { should match(/^Added org1 again { }$/) } + its('stderr') { should match(/^Issue creating org: {org1 organization1 } POST https:\/\/localhost\/organizations: 409$/) } + its('stdout') { should match(/^Added org2 {org2-validator -----BEGIN RSA PRIVATE KEY-----.*$/) } + its('stdout') { should match(/^Get org1 {org1 organization1 [0-9a-f]+}$/) } + its('stdout') { should match(/^List organizations After adding org1 and org2 map(?=.*org2:https:\/\/localhost\/organizations\/org2)(?=.*test:https:\/\/localhost\/organizations\/test)(?=.*org1:https:\/\/localhost\/organizations\/org1)/) } + its('stdout') { should match(/^Update org1 {org1 new_organization1 }/) } + its('stdout') { should match(/^Get org1 after update {org1 new_organization1 [0-9a-f]+}/) } + its('stdout') { should match(/^Delete org2 /) } + its('stdout') { should match(/^List organizations after deleting org2 map(?=.*test:https:\/\/localhost\/organizations\/test)(?=.*org1:https:\/\/localhost\/organizations\/org1)/) } + its('stdout') { should match(/^Result from deleting org1 /) } + its('stdout') { should match(/^List organizations after cleanup map\[test:https:\/\/localhost\/organizations\/test\]/) } +end diff --git a/examples/code b/examples/code new file mode 120000 index 0000000..3f3cece --- /dev/null +++ b/examples/code @@ -0,0 +1 @@ +./chefapi_examples/files/default/go/src/chefapi_test/cmd \ No newline at end of file diff --git a/examples/cookbooks/cookbooks.go b/examples/old/cookbooks/cookbooks.go similarity index 100% rename from examples/cookbooks/cookbooks.go rename to examples/old/cookbooks/cookbooks.go diff --git a/examples/cookbooks/key.pem b/examples/old/cookbooks/key.pem similarity index 100% rename from examples/cookbooks/key.pem rename to examples/old/cookbooks/key.pem diff --git a/examples/nodes/key.pem b/examples/old/nodes/key.pem similarity index 100% rename from examples/nodes/key.pem rename to examples/old/nodes/key.pem diff --git a/examples/nodes/nodes.go b/examples/old/nodes/nodes.go similarity index 100% rename from examples/nodes/nodes.go rename to examples/old/nodes/nodes.go diff --git a/examples/sandboxes/key.pem b/examples/old/sandboxes/key.pem similarity index 100% rename from examples/sandboxes/key.pem rename to examples/old/sandboxes/key.pem diff --git a/examples/sandboxes/sandboxes.go b/examples/old/sandboxes/sandboxes.go similarity index 100% rename from examples/sandboxes/sandboxes.go rename to examples/old/sandboxes/sandboxes.go diff --git a/examples/search/key.pem b/examples/old/search/key.pem similarity index 100% rename from examples/search/key.pem rename to examples/old/search/key.pem diff --git a/examples/search/search.go b/examples/old/search/search.go similarity index 100% rename from examples/search/search.go rename to examples/old/search/search.go From d61e8b5c58e9f8a71062535e0b9cdbaf34689e1a Mon Sep 17 00:00:00 2001 From: markgibbons Date: Thu, 26 Dec 2019 06:55:11 -0800 Subject: [PATCH 18/65] Moved the buildClient code to it's own package. This code will be used over and over in the examples. --- README.md | 4 +- .../src/chefapi_test/cmd/cookbook/cookbook.go | 148 ++---------------- .../cmd/organization/organization.go | 20 +-- examples/old/cookbooks/cookbooks.go | 59 ------- examples/old/cookbooks/key.pem | 27 ---- examples/old/nodes/key.pem | 27 ---- examples/old/nodes/nodes.go | 86 ---------- examples/old/sandboxes/key.pem | 27 ---- examples/old/sandboxes/sandboxes.go | 103 ------------ examples/old/search/key.pem | 27 ---- examples/old/search/search.go | 89 ----------- 11 files changed, 21 insertions(+), 596 deletions(-) delete mode 100644 examples/old/cookbooks/cookbooks.go delete mode 100644 examples/old/cookbooks/key.pem delete mode 100644 examples/old/nodes/key.pem delete mode 100644 examples/old/nodes/nodes.go delete mode 100644 examples/old/sandboxes/key.pem delete mode 100644 examples/old/sandboxes/sandboxes.go delete mode 100644 examples/old/search/key.pem delete mode 100644 examples/old/search/search.go diff --git a/README.md b/README.md index fc42833..1882bcb 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,14 @@ This is a Library that you can use to write tools to interact with the chef serv go get -t github.com/go-chef/chef go test -v github.com/go-chef/chef + examples::chefapi_tester kitchen verify ## SSL If you run into an SSL verification problem when trying to connect to a ssl server with self signed certs setup your config object with `SkipSSL: true` ## Usage -This example is setting up a basic client that you can use to interact with all the service endpoints (clients, nodes, cookbooks, etc.) +This example is setting up a basic client that you can use to interact with all the service endpoints (clients, nodes, cookbooks, etc. At [@chefapi](https://docs.chef.io/api_chef_server.html)) More usage examples can be found in the [examples](examples) directory. ```go @@ -79,6 +80,7 @@ improved, but is still an ongoing concern. |AJ Christensen |[@fujin](https://github.com/fujin) |Brad Beam |[@bradbeam](https://github.com/bradbeam) |Kraig Amador |[@bigkraig](https://github.com/bigkraig) +|Mark Gibbons |[@mark](https://github.com/markgibbons) ## COPYRIGHT diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go index dc5fce3..e4c670f 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go @@ -15,12 +15,11 @@ import ( // main Exercise the chef server api func main() { - // Pass in the database and chef-server api credentials. - usr1 := "usr1" - usr2 := "usr2" + # values passed in and client are common code, use struct and shared user := os.Args[1] keyfile := os.Args[2] chefurl := os.Args[3] + ssl // Create a client for user access client := buildClient(user, keyfile, chefurl) @@ -31,84 +30,16 @@ func main() { cookbookList := listCookbooks(client) fmt.Println(cookbookList)) - fmt.Println("") - fmt.Println("Added usr1") - userResult := createUser(client, chef.User{ UserName: usr1, DisplayName: "Show Me", Email: "user1@domain.io", FirstName: "user1", FullName: "All the Name", LastName: "fullname", MiddleName: "J", Password: "Logn12ComplexPwd#" }) - fmt.Println(userResult) - - fmt.Println("") - fmt.Println("Filter users email=user1") - userList = listUsers(client, "email=user1@domain.io") // map[string]string a1px:https://chefp01.nordstrom.net/users/a1px - fmt.Println(userList) - - fmt.Println("") - fmt.Println("Verbose users") - userVerboseOut := listUsersVerbose(client) // []UserVerbose - fmt.Printf("Verbose Out %v\n", userVerboseOut) - - err := client.AuthenticateUser.Authenticate(chef.AuthenticateUser{ UserName: usr1, Password: "Logn12ComplexPwd#" }) - fmt.Println("") - fmt.Println("") - fmt.Println("Error returned from authenticate: ", err) - - fmt.Println("") - fmt.Println("Added usr1 again") - userResult = createUser(client, chef.User{ UserName: usr1, Email: "user1@domain.io", FirstName: "user1", LastName: "fullname", DisplayName: "User1 Fullname", Password: "mary" }) - fmt.Println(userResult) - - fmt.Println("") - fmt.Println("Added usr2") - userResult = createUser(client, chef.User{ UserName: usr2, Email: "user2@domain.io", FirstName: "User2", LastName: "Fullname", DisplayName: "User2 Fullname", ExternalAuthenticationUid: "mary" }) - fmt.Println(userResult) - - fmt.Println("") - fmt.Println("Filter users external_authentication_uid=mary") - userList = listUsers(client, "external_authentication_uid=mary") - fmt.Println(userList) - - fmt.Println("") - fmt.Println("Original usr1") - userout := getUser(client, usr1) - fmt.Println(userout) - - fmt.Println("") - fmt.Println("Original pivotal") - userout = getUser(client, "pivotal") - fmt.Println(userout) - - fmt.Println("") - fmt.Println("After adding usr1 and usr2") - userList = listUsers(client) - fmt.Println(userList) - - fmt.Println("") - fmt.Println("After updating usr1") - userbody := chef.User{ FullName: "usr1new" } - fmt.Println("User request", userbody) - userresult := updateUser(client, "usr1", userbody) - fmt.Println(userresult) - - fmt.Println("") - fmt.Println("Get usr1") - userout = getUser(client, usr1) - fmt.Println(userout) - - fmt.Println("") - fmt.Println("Delete usr2") - userd, userErr := deleteUser(client, usr2) - fmt.Println(userErr) - fmt.Println("delete data", userd) - - fmt.Println("") - fmt.Println("Delete usr1") - userd, userErr = deleteUser(client, usr1) - fmt.Println(userErr) - fmt.Println("delete data", userd) - - fmt.Println("") - fmt.Println("list after deleting users") - userList = listUsers(client) - fmt.Println(userList) + // cookbooks GET + // List cookbooks + cookList, err := client.Cookbooks.List() + if err != nil { + fmt.Fprintln(os.STDERR, "Issue listing cookbooks:", err) + } + // cookbooks/_laters GET + // cookbooks/_recipes GET + // cookbooks/NAME GET + // cookbooks/NAME/VERSION DELETE, GET, PUT } @@ -138,51 +69,6 @@ func clientKey(filepath string) string { return string(key) } -// createOrganization uses the chef server api to create a single organization -func createOrganization(client *chef.Client, org chef.Organization) chef.OrganizationResult { - orgResult, err := client.Organizations.Create(org) - if err != nil { - fmt.Println("Issue creating org:", err) - } - return orgResult -} - -// deleteOrganization uses the chef server api to delete a single organization -func deleteOrganization(client *chef.Client, name string) error { - err := client.Organizations.Delete(name) - if err != nil { - fmt.Println("Issue deleting org:", err) - } - return err -} - -// createUser uses the chef server api to create a single organization -func createUser(client *chef.Client, user chef.User) chef.UserResult { - usrResult, err := client.Users.Create(user) - if err != nil { - fmt.Println("Issue creating user:", err) - } - return usrResult -} - -// deleteUser uses the chef server api to delete a single organization -func deleteUser(client *chef.Client, name string) (data chef.UserResult, err error) { - err = client.Users.Delete(name) - if err != nil { - fmt.Println("Issue deleting org:", err) - } - return -} - -// getUser uses the chef server api to get information for a single user -func getUser(client *chef.Client, name string) chef.User { - userList, err := client.Users.Get(name) - if err != nil { - fmt.Println("Issue listing user", err) - } - return userList -} - // listCookbooks uses the chef server api to list all cookbooks func listCookbooks(client *chef.Client, filters ...string) map[string]string { var filter string @@ -195,13 +81,3 @@ func listCookbooks(client *chef.Client, filters ...string) map[string]string { } return userList } - -// listUsersVerbose uses the chef server api to list all users and return verbose output -func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseItem { - userList, err := client.Users.ListVerbose() - fmt.Println("VERBOSE LIST", userList) - if err != nil { - fmt.Println("Issue listing verbose users:", err) - } - return userList -} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go index cc6b7fc..e298fc3 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go @@ -7,29 +7,21 @@ import ( "fmt" "io/ioutil" "os" - "strconv" - chef "github.com/go-chef/chef" + "github.com/go-chef/chef" + "chefapi_test/testapi" ) // main Exercise the chef server api func main() { - // Pass in the database and chef-server api credentials. - org1 := "org1" - org2 := "org2" - user := os.Args[1] - keyfile := os.Args[2] - chefurl := os.Args[3] - skipssl, err := strconv.ParseBool(os.Args[4]) - if err != nil { - skipssl = true - } - // Create a client for access - client := buildClient(user, keyfile, chefurl, skipssl) + client := testapi.Client() // Organization tests + org1 := "org1" + org2 := "org2" + orgList := listOrganizations(client) fmt.Println("List initial organizations", orgList) diff --git a/examples/old/cookbooks/cookbooks.go b/examples/old/cookbooks/cookbooks.go deleted file mode 100644 index d9cb1eb..0000000 --- a/examples/old/cookbooks/cookbooks.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - - "github.com/go-chef/chef" -) - -func main() { - // simple arg parsing - //cookPath := flag.String("cookbook", "c", "Path to cookbook for upload") - // flag.Parse() - - // read a client key - key, err := ioutil.ReadFile("key.pem") - if err != nil { - fmt.Println("Couldn't read key.pem:", err) - os.Exit(1) - } - - // build a client - client, err := chef.NewClient(&chef.Config{ - Name: "foo", - Key: string(key), - // goiardi is on port 4545 by default. chef-zero is 8889 - BaseURL: "http://localhost:8443", - }) - if err != nil { - fmt.Println("Issue setting up client:", err) - os.Exit(1) - } - - // List Cookbooks - cookList, err := client.Cookbooks.List() - if err != nil { - fmt.Println("Issue listing cookbooks:", err) - } - - // Print out the list - fmt.Println(cookList) - /* - *'parse' metadata... - this would prefer .json over .rb - if it's .rb lets maybe try to eval it ? - otherwise just extract name/version if they exist - */ - - /* - - - * generate sums - * create sandbox - * upload to sandbox - * - */ - -} diff --git a/examples/old/cookbooks/key.pem b/examples/old/cookbooks/key.pem deleted file mode 100644 index f44ac76..0000000 --- a/examples/old/cookbooks/key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAx12nDxxOwSPHRSJEDz67a0folBqElzlu2oGMiUTS+dqtj3FU -h5lJc1MjcprRVxcDVwhsSSo9948XEkk39IdblUCLohucqNMzOnIcdZn8zblN7Cnp -W03UwRM0iWX1HuwHnGvm6PKeqKGqplyIXYO0qlDWCzC+VaxFTwOUk31MfOHJQn4y -fTrfuE7h3FTElLBu065SFp3dPICIEmWCl9DadnxbnZ8ASxYQ9xG7hmZduDgjNW5l -3x6/EFkpym+//D6AbWDcVJ1ovCsJL3CfH/NZC3ekeJ/aEeLxP/vaCSH1VYC5VsYK -5Qg7SIa6Nth3+RZz1hYOoBJulEzwljznwoZYRQIDAQABAoIBADPQol+qAsnty5er -PTcdHcbXLJp5feZz1dzSeL0gdxja/erfEJIhg9aGUBs0I55X69VN6h7l7K8PsHZf -MzzJhUL4QJJETOYP5iuVhtIF0I+DTr5Hck/5nYcEv83KAvgjbiL4ZE486IF5awnL -2OE9HtJ5KfhEleNcX7MWgiIHGb8G1jCqu/tH0GI8Z4cNgUrXMbczGwfbN/5Wc0zo -Dtpe0Tec/Fd0DLFwRiAuheakPjlVWb7AGMDX4TyzCXfMpS1ul2jk6nGFk77uQozF -PQUawCRp+mVS4qecgq/WqfTZZbBlW2L18/kpafvsxG8kJ7OREtrb0SloZNFHEc2Q -70GbgKECgYEA6c/eOrI3Uour1gKezEBFmFKFH6YS/NZNpcSG5PcoqF6AVJwXg574 -Qy6RatC47e92be2TT1Oyplntj4vkZ3REv81yfz/tuXmtG0AylH7REbxubxAgYmUT -18wUAL4s3TST2AlK4R29KwBadwUAJeOLNW+Rc4xht1galsqQRb4pUzkCgYEA2kj2 -vUhKAB7QFCPST45/5q+AATut8WeHnI+t1UaiZoK41Jre8TwlYqUgcJ16Q0H6KIbJ -jlEZAu0IsJxjQxkD4oJgv8n5PFXdc14HcSQ512FmgCGNwtDY/AT7SQP3kOj0Rydg -N02uuRb/55NJ07Bh+yTQNGA+M5SSnUyaRPIAMW0CgYBgVU7grDDzB60C/g1jZk/G -VKmYwposJjfTxsc1a0gLJvSE59MgXc04EOXFNr4a+oC3Bh2dn4SJ2Z9xd1fh8Bur -UwCLwVE3DBTwl2C/ogiN4C83/1L4d2DXlrPfInvloBYR+rIpUlFweDLNuve2pKvk -llU9YGeaXOiHnGoY8iKgsQKBgQDZKMOHtZYhHoZlsul0ylCGAEz5bRT0V8n7QJlw -12+TSjN1F4n6Npr+00Y9ov1SUh38GXQFiLq4RXZitYKu6wEJZCm6Q8YXd1jzgDUp -IyAEHNsrV7Y/fSSRPKd9kVvGp2r2Kr825aqQasg16zsERbKEdrBHmwPmrsVZhi7n -rlXw1QKBgQDBOyUJKQOgDE2u9EHybhCIbfowyIE22qn9a3WjQgfxFJ+aAL9Bg124 -fJIEzz43fJ91fe5lTOgyMF5TtU5ClAOPGtlWnXU0e5j3L4LjbcqzEbeyxvP3sn1z -dYkX7NdNQ5E6tcJZuJCGq0HxIAQeKPf3x9DRKzMnLply6BEzyuAC4g== ------END RSA PRIVATE KEY----- diff --git a/examples/old/nodes/key.pem b/examples/old/nodes/key.pem deleted file mode 100644 index f44ac76..0000000 --- a/examples/old/nodes/key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAx12nDxxOwSPHRSJEDz67a0folBqElzlu2oGMiUTS+dqtj3FU -h5lJc1MjcprRVxcDVwhsSSo9948XEkk39IdblUCLohucqNMzOnIcdZn8zblN7Cnp -W03UwRM0iWX1HuwHnGvm6PKeqKGqplyIXYO0qlDWCzC+VaxFTwOUk31MfOHJQn4y -fTrfuE7h3FTElLBu065SFp3dPICIEmWCl9DadnxbnZ8ASxYQ9xG7hmZduDgjNW5l -3x6/EFkpym+//D6AbWDcVJ1ovCsJL3CfH/NZC3ekeJ/aEeLxP/vaCSH1VYC5VsYK -5Qg7SIa6Nth3+RZz1hYOoBJulEzwljznwoZYRQIDAQABAoIBADPQol+qAsnty5er -PTcdHcbXLJp5feZz1dzSeL0gdxja/erfEJIhg9aGUBs0I55X69VN6h7l7K8PsHZf -MzzJhUL4QJJETOYP5iuVhtIF0I+DTr5Hck/5nYcEv83KAvgjbiL4ZE486IF5awnL -2OE9HtJ5KfhEleNcX7MWgiIHGb8G1jCqu/tH0GI8Z4cNgUrXMbczGwfbN/5Wc0zo -Dtpe0Tec/Fd0DLFwRiAuheakPjlVWb7AGMDX4TyzCXfMpS1ul2jk6nGFk77uQozF -PQUawCRp+mVS4qecgq/WqfTZZbBlW2L18/kpafvsxG8kJ7OREtrb0SloZNFHEc2Q -70GbgKECgYEA6c/eOrI3Uour1gKezEBFmFKFH6YS/NZNpcSG5PcoqF6AVJwXg574 -Qy6RatC47e92be2TT1Oyplntj4vkZ3REv81yfz/tuXmtG0AylH7REbxubxAgYmUT -18wUAL4s3TST2AlK4R29KwBadwUAJeOLNW+Rc4xht1galsqQRb4pUzkCgYEA2kj2 -vUhKAB7QFCPST45/5q+AATut8WeHnI+t1UaiZoK41Jre8TwlYqUgcJ16Q0H6KIbJ -jlEZAu0IsJxjQxkD4oJgv8n5PFXdc14HcSQ512FmgCGNwtDY/AT7SQP3kOj0Rydg -N02uuRb/55NJ07Bh+yTQNGA+M5SSnUyaRPIAMW0CgYBgVU7grDDzB60C/g1jZk/G -VKmYwposJjfTxsc1a0gLJvSE59MgXc04EOXFNr4a+oC3Bh2dn4SJ2Z9xd1fh8Bur -UwCLwVE3DBTwl2C/ogiN4C83/1L4d2DXlrPfInvloBYR+rIpUlFweDLNuve2pKvk -llU9YGeaXOiHnGoY8iKgsQKBgQDZKMOHtZYhHoZlsul0ylCGAEz5bRT0V8n7QJlw -12+TSjN1F4n6Npr+00Y9ov1SUh38GXQFiLq4RXZitYKu6wEJZCm6Q8YXd1jzgDUp -IyAEHNsrV7Y/fSSRPKd9kVvGp2r2Kr825aqQasg16zsERbKEdrBHmwPmrsVZhi7n -rlXw1QKBgQDBOyUJKQOgDE2u9EHybhCIbfowyIE22qn9a3WjQgfxFJ+aAL9Bg124 -fJIEzz43fJ91fe5lTOgyMF5TtU5ClAOPGtlWnXU0e5j3L4LjbcqzEbeyxvP3sn1z -dYkX7NdNQ5E6tcJZuJCGq0HxIAQeKPf3x9DRKzMnLply6BEzyuAC4g== ------END RSA PRIVATE KEY----- diff --git a/examples/old/nodes/nodes.go b/examples/old/nodes/nodes.go deleted file mode 100644 index 9262bec..0000000 --- a/examples/old/nodes/nodes.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "os" - - "github.com/go-chef/chef" -) - -func main() { - // read a client key - key, err := ioutil.ReadFile("key.pem") - if err != nil { - fmt.Println("Couldn't read key.pem:", err) - os.Exit(1) - } - - // build a client - client, err := chef.NewClient(&chef.Config{ - Name: "foo", - Key: string(key), - // goiardi is on port 4545 by default. chef-zero is 8889 - BaseURL: "http://localhost:4545", - }) - if err != nil { - fmt.Println("Issue setting up client:", err) - os.Exit(1) - } - - // Create a Node object - // TOOD: should have a constructor for this - ranjib := chef.Node{ - Name: "ranjib", - Environment: "_default", - ChefType: "node", - JsonClass: "Chef::Node", - RunList: []string{"pwn"}, - } - - // Delete node ignoring errors :) - client.Nodes.Delete(ranjib.Name) - - // Create - _, err = client.Nodes.Post(ranjib) - if err != nil { - log.Fatal("Couldn't create node. ", err) - } - - // List nodes - nodeList, err := client.Nodes.List() - if err != nil { - log.Fatal("Couldn't list nodes: ", err) - } - - // dump the node list in Json - jsonData, err := json.MarshalIndent(nodeList, "", "\t") - os.Stdout.Write(jsonData) - os.Stdout.WriteString("\n") - - // dump the ranjib node we got from server in JSON! - serverNode, _ := client.Nodes.Get("ranjib") - if err != nil { - log.Fatal("Couldn't get node: ", err) - } - jsonData, err = json.MarshalIndent(serverNode, "", "\t") - os.Stdout.Write(jsonData) - os.Stdout.WriteString("\n") - - // update node - ranjib.RunList = append(ranjib.RunList, "recipe[works]") - jsonData, err = json.MarshalIndent(ranjib, "", "\t") - os.Stdout.Write(jsonData) - os.Stdout.WriteString("\n") - - _, err = client.Nodes.Put(ranjib) - if err != nil { - log.Fatal("Couldn't update node: ", err) - } - - // Delete node ignoring errors :) - client.Nodes.Delete(ranjib.Name) - -} diff --git a/examples/old/sandboxes/key.pem b/examples/old/sandboxes/key.pem deleted file mode 100644 index f44ac76..0000000 --- a/examples/old/sandboxes/key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAx12nDxxOwSPHRSJEDz67a0folBqElzlu2oGMiUTS+dqtj3FU -h5lJc1MjcprRVxcDVwhsSSo9948XEkk39IdblUCLohucqNMzOnIcdZn8zblN7Cnp -W03UwRM0iWX1HuwHnGvm6PKeqKGqplyIXYO0qlDWCzC+VaxFTwOUk31MfOHJQn4y -fTrfuE7h3FTElLBu065SFp3dPICIEmWCl9DadnxbnZ8ASxYQ9xG7hmZduDgjNW5l -3x6/EFkpym+//D6AbWDcVJ1ovCsJL3CfH/NZC3ekeJ/aEeLxP/vaCSH1VYC5VsYK -5Qg7SIa6Nth3+RZz1hYOoBJulEzwljznwoZYRQIDAQABAoIBADPQol+qAsnty5er -PTcdHcbXLJp5feZz1dzSeL0gdxja/erfEJIhg9aGUBs0I55X69VN6h7l7K8PsHZf -MzzJhUL4QJJETOYP5iuVhtIF0I+DTr5Hck/5nYcEv83KAvgjbiL4ZE486IF5awnL -2OE9HtJ5KfhEleNcX7MWgiIHGb8G1jCqu/tH0GI8Z4cNgUrXMbczGwfbN/5Wc0zo -Dtpe0Tec/Fd0DLFwRiAuheakPjlVWb7AGMDX4TyzCXfMpS1ul2jk6nGFk77uQozF -PQUawCRp+mVS4qecgq/WqfTZZbBlW2L18/kpafvsxG8kJ7OREtrb0SloZNFHEc2Q -70GbgKECgYEA6c/eOrI3Uour1gKezEBFmFKFH6YS/NZNpcSG5PcoqF6AVJwXg574 -Qy6RatC47e92be2TT1Oyplntj4vkZ3REv81yfz/tuXmtG0AylH7REbxubxAgYmUT -18wUAL4s3TST2AlK4R29KwBadwUAJeOLNW+Rc4xht1galsqQRb4pUzkCgYEA2kj2 -vUhKAB7QFCPST45/5q+AATut8WeHnI+t1UaiZoK41Jre8TwlYqUgcJ16Q0H6KIbJ -jlEZAu0IsJxjQxkD4oJgv8n5PFXdc14HcSQ512FmgCGNwtDY/AT7SQP3kOj0Rydg -N02uuRb/55NJ07Bh+yTQNGA+M5SSnUyaRPIAMW0CgYBgVU7grDDzB60C/g1jZk/G -VKmYwposJjfTxsc1a0gLJvSE59MgXc04EOXFNr4a+oC3Bh2dn4SJ2Z9xd1fh8Bur -UwCLwVE3DBTwl2C/ogiN4C83/1L4d2DXlrPfInvloBYR+rIpUlFweDLNuve2pKvk -llU9YGeaXOiHnGoY8iKgsQKBgQDZKMOHtZYhHoZlsul0ylCGAEz5bRT0V8n7QJlw -12+TSjN1F4n6Npr+00Y9ov1SUh38GXQFiLq4RXZitYKu6wEJZCm6Q8YXd1jzgDUp -IyAEHNsrV7Y/fSSRPKd9kVvGp2r2Kr825aqQasg16zsERbKEdrBHmwPmrsVZhi7n -rlXw1QKBgQDBOyUJKQOgDE2u9EHybhCIbfowyIE22qn9a3WjQgfxFJ+aAL9Bg124 -fJIEzz43fJ91fe5lTOgyMF5TtU5ClAOPGtlWnXU0e5j3L4LjbcqzEbeyxvP3sn1z -dYkX7NdNQ5E6tcJZuJCGq0HxIAQeKPf3x9DRKzMnLply6BEzyuAC4g== ------END RSA PRIVATE KEY----- diff --git a/examples/old/sandboxes/sandboxes.go b/examples/old/sandboxes/sandboxes.go deleted file mode 100644 index 21d06b5..0000000 --- a/examples/old/sandboxes/sandboxes.go +++ /dev/null @@ -1,103 +0,0 @@ -package main - -import ( - "bytes" - "crypto/md5" - "crypto/rand" - "encoding/json" - "fmt" - "io/ioutil" - "os" - - "github.com/cenkalti/backoff" - "github.com/davecgh/go-spew/spew" - "github.com/go-chef/chef" -) - -//random_data makes random byte slice for building junk sandbox data -func random_data(size int) (b []byte) { - b = make([]byte, size) - rand.Read(b) - return -} - -func main() { - // read a client key - key, err := ioutil.ReadFile("key.pem") - if err != nil { - fmt.Println("Couldn't read key.pem:", err) - os.Exit(1) - } - - // build a client - client, err := chef.NewClient(&chef.Config{ - Name: "foo", - Key: string(key), - // goiardi is on port 4545 by default. chef-zero is 8889 - BaseURL: "http://localhost:4545", - }) - if err != nil { - fmt.Println("Issue setting up client:", err) - os.Exit(1) - } - - // create junk files and sums - files := make(map[string][]byte) - sums := make([]string, 10) - for i := 0; i < 10; i++ { - data := random_data(1024) - hashstr := fmt.Sprintf("%x", md5.Sum(data)) - files[hashstr] = data - sums[i] = hashstr - } - - // post the new sums/files to the sandbox - postResp, err := client.Sandboxes.Post(sums) - if err != nil { - fmt.Println("error making request: ", err) - os.Exit(1) - } - - // Dump the the server response data - j, err := json.MarshalIndent(postResp, "", " ") - fmt.Printf("%s", j) - - // Let's upload the files that postRep thinks we should upload - for hash, item := range postResp.Checksums { - if item.Upload == true { - if hash == "" { - continue - } - // If you were writing this in your own tool you could just use the FH and let the Reader interface suck out the content instead of doing the convert. - fmt.Printf("\nUploading: %s ---> %v\n\n", hash, item) - req, err := client.NewRequest("PUT", item.Url, bytes.NewReader(files[hash])) - if err != nil { - fmt.Println("This shouldn't happen:", err.Error()) - os.Exit(1) - } - - // post the files - upload := func() error { - _, err = client.Do(req, nil) - return err - } - - // with exp backoff ! - err = backoff.Retry(upload, backoff.NewExponentialBackOff()) - if err != nil { - fmt.Println("error posting files to the sandbox: ", err.Error()) - } - } - } - - // Now lets tell the server we have uploaded all the things. - sandbox, err := client.Sandboxes.Put(postResp.ID) - if err != nil { - fmt.Println("Error commiting sandbox: ", err.Error()) - os.Exit(1) - } - - // and heres yer commited sandbox - spew.Dump(sandbox) - -} diff --git a/examples/old/search/key.pem b/examples/old/search/key.pem deleted file mode 100644 index f44ac76..0000000 --- a/examples/old/search/key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAx12nDxxOwSPHRSJEDz67a0folBqElzlu2oGMiUTS+dqtj3FU -h5lJc1MjcprRVxcDVwhsSSo9948XEkk39IdblUCLohucqNMzOnIcdZn8zblN7Cnp -W03UwRM0iWX1HuwHnGvm6PKeqKGqplyIXYO0qlDWCzC+VaxFTwOUk31MfOHJQn4y -fTrfuE7h3FTElLBu065SFp3dPICIEmWCl9DadnxbnZ8ASxYQ9xG7hmZduDgjNW5l -3x6/EFkpym+//D6AbWDcVJ1ovCsJL3CfH/NZC3ekeJ/aEeLxP/vaCSH1VYC5VsYK -5Qg7SIa6Nth3+RZz1hYOoBJulEzwljznwoZYRQIDAQABAoIBADPQol+qAsnty5er -PTcdHcbXLJp5feZz1dzSeL0gdxja/erfEJIhg9aGUBs0I55X69VN6h7l7K8PsHZf -MzzJhUL4QJJETOYP5iuVhtIF0I+DTr5Hck/5nYcEv83KAvgjbiL4ZE486IF5awnL -2OE9HtJ5KfhEleNcX7MWgiIHGb8G1jCqu/tH0GI8Z4cNgUrXMbczGwfbN/5Wc0zo -Dtpe0Tec/Fd0DLFwRiAuheakPjlVWb7AGMDX4TyzCXfMpS1ul2jk6nGFk77uQozF -PQUawCRp+mVS4qecgq/WqfTZZbBlW2L18/kpafvsxG8kJ7OREtrb0SloZNFHEc2Q -70GbgKECgYEA6c/eOrI3Uour1gKezEBFmFKFH6YS/NZNpcSG5PcoqF6AVJwXg574 -Qy6RatC47e92be2TT1Oyplntj4vkZ3REv81yfz/tuXmtG0AylH7REbxubxAgYmUT -18wUAL4s3TST2AlK4R29KwBadwUAJeOLNW+Rc4xht1galsqQRb4pUzkCgYEA2kj2 -vUhKAB7QFCPST45/5q+AATut8WeHnI+t1UaiZoK41Jre8TwlYqUgcJ16Q0H6KIbJ -jlEZAu0IsJxjQxkD4oJgv8n5PFXdc14HcSQ512FmgCGNwtDY/AT7SQP3kOj0Rydg -N02uuRb/55NJ07Bh+yTQNGA+M5SSnUyaRPIAMW0CgYBgVU7grDDzB60C/g1jZk/G -VKmYwposJjfTxsc1a0gLJvSE59MgXc04EOXFNr4a+oC3Bh2dn4SJ2Z9xd1fh8Bur -UwCLwVE3DBTwl2C/ogiN4C83/1L4d2DXlrPfInvloBYR+rIpUlFweDLNuve2pKvk -llU9YGeaXOiHnGoY8iKgsQKBgQDZKMOHtZYhHoZlsul0ylCGAEz5bRT0V8n7QJlw -12+TSjN1F4n6Npr+00Y9ov1SUh38GXQFiLq4RXZitYKu6wEJZCm6Q8YXd1jzgDUp -IyAEHNsrV7Y/fSSRPKd9kVvGp2r2Kr825aqQasg16zsERbKEdrBHmwPmrsVZhi7n -rlXw1QKBgQDBOyUJKQOgDE2u9EHybhCIbfowyIE22qn9a3WjQgfxFJ+aAL9Bg124 -fJIEzz43fJ91fe5lTOgyMF5TtU5ClAOPGtlWnXU0e5j3L4LjbcqzEbeyxvP3sn1z -dYkX7NdNQ5E6tcJZuJCGq0HxIAQeKPf3x9DRKzMnLply6BEzyuAC4g== ------END RSA PRIVATE KEY----- diff --git a/examples/old/search/search.go b/examples/old/search/search.go deleted file mode 100644 index 0f1e16a..0000000 --- a/examples/old/search/search.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "os" - - "github.com/davecgh/go-spew/spew" - "github.com/go-chef/chef" -) - -func main() { - // read a client key - key, err := ioutil.ReadFile("key.pem") - if err != nil { - fmt.Println("Couldn't read key.pem:", err) - os.Exit(1) - } - - // build a client - client, err := chef.NewClient(&chef.Config{ - Name: "foo", - Key: string(key), - // goiardi is on port 4545 by default. chef-zero is 8889 - BaseURL: "http://localhost:4545", - }) - if err != nil { - fmt.Println("Issue setting up client:", err) - os.Exit(1) - } - - // List Indexes - indexes, err := client.Search.Indexes() - if err != nil { - log.Fatal("Couldn't list nodes: ", err) - } - - // dump the Index list in Json - jsonData, err := json.MarshalIndent(indexes, "", "\t") - os.Stdout.Write(jsonData) - os.Stdout.WriteString("\n") - - // build a seach query - query, err := client.Search.NewQuery("node", "name:*") - if err != nil { - log.Fatal("Error building query ", err) - } - - // Run the query - res, err := query.Do(client) - if err != nil { - log.Fatal("Error running query ", err) - } - - // <3 spew - spew.Dump(res) - - // dump out results back in json for fun - jsonData, err = json.MarshalIndent(res, "", "\t") - os.Stdout.Write(jsonData) - os.Stdout.WriteString("\n") - - // You can also use the service to run a query - res, err = client.Search.Exec("node", "name:*") - if err != nil { - log.Fatal("Error running Search.Exec() ", err) - } - - // dump out results back in json for fun - jsonData, err = json.MarshalIndent(res, "", "\t") - os.Stdout.Write(jsonData) - os.Stdout.WriteString("\n") - - // Partial search - log.Print("Partial Search") - part := make(map[string]interface{}) - part["name"] = []string{"name"} - pres, err := client.Search.PartialExec("node", "*:*", part) - if err != nil { - log.Fatal("Error running Search.PartialExec()", err) - } - - jsonData, err = json.MarshalIndent(pres, "", "\t") - os.Stdout.Write(jsonData) - os.Stdout.WriteString("\n") - -} From 08da3ff2f2e2c2c768f53bd100fff9e417c1927e Mon Sep 17 00:00:00 2001 From: markgibbons Date: Fri, 27 Dec 2019 05:39:13 -0800 Subject: [PATCH 19/65] Add an example and tests for using node.go --- .gitignore | 3 + examples/chefapi_examples/.gitignore | 21 ----- examples/chefapi_examples/Berksfile.lock | 15 ++++ .../default/go/src/chefapi_test/Testing.md | 5 ++ .../default/go/src/chefapi_test/bin/creds | 5 +- .../default/go/src/chefapi_test/bin/node | 10 +++ .../src/chefapi_test/cmd/cookbook/cookbook.go | 6 -- .../go/src/chefapi_test/cmd/node/nodes.go | 84 +++++++++++++++++++ .../go/src/chefapi_test/testapi/testapi.go | 55 ++++++++++++ examples/chefapi_examples/metadata.rb | 1 - examples/chefapi_examples/recipes/chefapi.rb | 2 +- .../integration/default/inspec/node_spec.rb | 15 ++++ .../default/inspec/organization_spec.rb | 8 +- 13 files changed, 195 insertions(+), 35 deletions(-) delete mode 100644 examples/chefapi_examples/.gitignore create mode 100644 examples/chefapi_examples/Berksfile.lock create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/Testing.md create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go create mode 100644 examples/chefapi_examples/test/integration/default/inspec/node_spec.rb diff --git a/.gitignore b/.gitignore index 392a11e..43efa59 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ _testmain.go # vim *.swp + +# cookbook +*.kitchen diff --git a/examples/chefapi_examples/.gitignore b/examples/chefapi_examples/.gitignore deleted file mode 100644 index febee30..0000000 --- a/examples/chefapi_examples/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -.vagrant -*~ -*# -.#* -\#*# -.*.sw[a-z] -*.un~ - -# Bundler -Gemfile.lock -bin/* -.bundle/* - -# test kitchen -.kitchen/ -.kitchen.local.yml - -# Chef -Berksfile.lock -.zero-knife.rb -Policyfile.lock.json diff --git a/examples/chefapi_examples/Berksfile.lock b/examples/chefapi_examples/Berksfile.lock new file mode 100644 index 0000000..c250eba --- /dev/null +++ b/examples/chefapi_examples/Berksfile.lock @@ -0,0 +1,15 @@ +DEPENDENCIES + chefapi_examples + path: . + metadata: true + +GRAPH + chef-ingredient (3.1.2) + chef-server (5.5.2) + chef-ingredient (>= 2.1.10) + chefapi_examples (0.1.0) + chef-server (>= 0.0.0) + fileutils (>= 0.0.0) + line (>= 0.0.0) + fileutils (1.3.1) + line (2.5.0) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/Testing.md b/examples/chefapi_examples/files/default/go/src/chefapi_test/Testing.md new file mode 100644 index 0000000..26e30ad --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/Testing.md @@ -0,0 +1,5 @@ +## Global +https://test + +## Organization specific +https://test/organization/test/* diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds index 0ebf4a2..5f9cd8f 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds @@ -1,4 +1,5 @@ CHEFUSER='pivotal' KEYFILE='/etc/opscode/pivotal.pem' -CHEFGLOBALURL='https://localhost' -CHEFORGURL='https://localhost' +CHEFGLOBALURL='https://localhost/' +# The trailing / on the url is critically important. The api does not work without it. +CHEFORGANIZATIONURL='https://localhost/organizations/test/' diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node new file mode 100755 index 0000000..c76f103 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node @@ -0,0 +1,10 @@ +#!/bin/bash + +# Node testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds + +go run ${BASE}/../cmd/node/nodes.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go index e4c670f..1fe4e85 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go @@ -15,12 +15,6 @@ import ( // main Exercise the chef server api func main() { - # values passed in and client are common code, use struct and shared - user := os.Args[1] - keyfile := os.Args[2] - chefurl := os.Args[3] - ssl - // Create a client for user access client := buildClient(user, keyfile, chefurl) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go new file mode 100644 index 0000000..c94cf5b --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go @@ -0,0 +1,84 @@ +package main + +import ( + "fmt" + "os" + + "github.com/go-chef/chef" + "chefapi_test/testapi" +) + +func main() { + // Use the default test org + client := testapi.Client() + fmt.Println("Client", client) + + // List initial nodes + nodeList, err := client.Nodes.List() + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't list nodes: ", err) + } + fmt.Println("List initial nodes", nodeList) + + // Define a Node object + node1 := chef.NewNode("node1") + node1.RunList = []string{"pwn"} + fmt.Println("Define node1", node1) + + // Delete node1 ignoring errors :) + err = client.Nodes.Delete(node1.Name) + + // Create + nodeResult, err := client.Nodes.Post(node1) + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't create node node1. ", err) + } + fmt.Println("Added node1", nodeResult) + + // List nodes + nodeList, err = client.Nodes.List() + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't list nodes: ", err) + } + fmt.Println("List nodes after adding node1", nodeList) + + // Create a second time + nodeResult, err = client.Nodes.Post(node1) + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't recreate node node1. ", err) + } + fmt.Println("Added node1", nodeResult) + + // Read node1 information + serverNode, err := client.Nodes.Get("node1") + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't get node: ", err) + } + fmt.Printf("Get node1 %+v\n", serverNode) + + // update node + node1.RunList = append(node1.RunList, "recipe[works]") + updateNode, err := client.Nodes.Put(node1) + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't update node: ", err) + } + fmt.Println("Update node1", updateNode) + + // Info after update + serverNode, err = client.Nodes.Get("node1") + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't get node: ", err) + } + fmt.Printf("Get node1 after update %+v\n", serverNode) + + // Delete node ignoring errors :) + err = client.Nodes.Delete(node1.Name) + fmt.Println("Delete node1", err) + + // List nodes + nodeList, err = client.Nodes.List() + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't list nodes: ", err) + } + fmt.Println("List nodes after cleanup", nodeList) +} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go new file mode 100644 index 0000000..b1365ab --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go @@ -0,0 +1,55 @@ +// +// Test the go-chef/chef chef server api /organizations endpoints against a live server +// +package testapi + +import ( + "fmt" + "io/ioutil" + "os" + "strconv" + + chef "github.com/go-chef/chef" +) + +// main Exercise the chef server api +func Client() *chef.Client { + // Pass in the database and chef-server api credentials. + user := os.Args[1] + keyfile := os.Args[2] + chefurl := os.Args[3] + skipssl, err := strconv.ParseBool(os.Args[4]) + if err != nil { + skipssl = true + } + + // Create a client for access + return buildClient(user, keyfile, chefurl, skipssl) +} + +// buildClient creates a connection to a chef server using the chef api. +func buildClient(user string, keyfile string, baseurl string, skipssl bool) *chef.Client { + key := clientKey(keyfile) + client, err := chef.NewClient(&chef.Config{ + Name: user, + Key: string(key), + BaseURL: baseurl, + SkipSSL: skipssl, + // goiardi uses port 4545 by default, chef-zero uses 8889, chef-server uses 443 + }) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue setting up client:", err) + os.Exit(1) + } + return client +} + +// clientKey reads the pem file containing the credentials needed to use the chef client. +func clientKey(filepath string) string { + key, err := ioutil.ReadFile(filepath) + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't read key.pem:", err) + os.Exit(1) + } + return string(key) +} diff --git a/examples/chefapi_examples/metadata.rb b/examples/chefapi_examples/metadata.rb index 0cdb1c1..de79d08 100644 --- a/examples/chefapi_examples/metadata.rb +++ b/examples/chefapi_examples/metadata.rb @@ -1,6 +1,5 @@ name 'chefapi_examples' maintainer 'go-chef/chef project maintainers' -maintainer_email 'you@example.com' license 'All Rights Reserved' description 'Test the chef server api' version '0.1.0' diff --git a/examples/chefapi_examples/recipes/chefapi.rb b/examples/chefapi_examples/recipes/chefapi.rb index 2adece0..558e8d6 100644 --- a/examples/chefapi_examples/recipes/chefapi.rb +++ b/examples/chefapi_examples/recipes/chefapi.rb @@ -15,7 +15,7 @@ end remote_directory 'local_go' do - files_backup false + files_backup false path '/go' purge false recursive true diff --git a/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb new file mode 100644 index 0000000..d868a7c --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb @@ -0,0 +1,15 @@ +# Inspec tests for the node chef api go module +# + +describe command('/go/src/chefapi_test/bin/node') do + its('stderr') { should match(%r{^Couldn't recreate node node1. POST https://localhost/organizations/test/nodes: 409}) } + its('stdout') { should match(/^List initial nodes map\[\]$/) } + its('stdout') { should match(/^Define node1 {node1 _default.*Chef::Node \[pwn\]/) } + its('stdout') { should match(%r{^Added node1 \&\{https://localhost/organizations/test/nodes/node1\}}) } + its('stdout') { should match(%r{^List nodes after adding node1 map\[node1:https://localhost/organizations/test/nodes/node1\]}) } + its('stdout') { should match(/^Get node1 {Name:node1 Environment:_default ChefType:node AutomaticAttributes:map\[\] NormalAttributes:map\[\] DefaultAttributes:map\[\] OverrideAttributes:map\[\] JsonClass:Chef::Node RunList:\[recipe\[pwn\]\] PolicyName: PolicyGroup/) } + its('stdout') { should match(/^Update node1/) } + its('stdout') { should match(/^Get node1 after update/) } + its('stdout') { should match(/^Delete node1 /) } + its('stdout') { should match(/^List nodes after cleanup map\[\]/) } +end diff --git a/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb index 8b39e15..fa662ab 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb @@ -5,14 +5,14 @@ its('stdout') { should match(/^List initial organizations map\[test.*test\]$/) } its('stdout') { should match(/^Added org1 {org1-validator -----BEGIN RSA PRIVATE KEY-----/) } its('stdout') { should match(/^Added org1 again { }$/) } - its('stderr') { should match(/^Issue creating org: {org1 organization1 } POST https:\/\/localhost\/organizations: 409$/) } + its('stderr') { should match(%r{^Issue creating org: {org1 organization1 } POST https://localhost/organizations: 409$}) } its('stdout') { should match(/^Added org2 {org2-validator -----BEGIN RSA PRIVATE KEY-----.*$/) } its('stdout') { should match(/^Get org1 {org1 organization1 [0-9a-f]+}$/) } - its('stdout') { should match(/^List organizations After adding org1 and org2 map(?=.*org2:https:\/\/localhost\/organizations\/org2)(?=.*test:https:\/\/localhost\/organizations\/test)(?=.*org1:https:\/\/localhost\/organizations\/org1)/) } + its('stdout') { should match(%r{^List organizations After adding org1 and org2 map(?=.*org2:https://localhost/organizations/org2)(?=.*test:https://localhost/organizations/test)(?=.*org1:https://localhost/organizations/org1)}) } its('stdout') { should match(/^Update org1 {org1 new_organization1 }/) } its('stdout') { should match(/^Get org1 after update {org1 new_organization1 [0-9a-f]+}/) } its('stdout') { should match(/^Delete org2 /) } - its('stdout') { should match(/^List organizations after deleting org2 map(?=.*test:https:\/\/localhost\/organizations\/test)(?=.*org1:https:\/\/localhost\/organizations\/org1)/) } + its('stdout') { should match(%r{^List organizations after deleting org2 map(?=.*test:https://localhost/organizations/test)(?=.*org1:https://localhost/organizations/org1)}) } its('stdout') { should match(/^Result from deleting org1 /) } - its('stdout') { should match(/^List organizations after cleanup map\[test:https:\/\/localhost\/organizations\/test\]/) } + its('stdout') { should match(%r{^List organizations after cleanup map\[test:https://localhost/organizations/test\]}) } end From 4ac52d67fc9a41a587a828084de07f018940ffcd Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 28 Dec 2019 13:28:23 -0800 Subject: [PATCH 20/65] Add code examples and tests for cookbook.go --- examples/README.md | 8 ++ .../0.1.0/sampbook}/README.md | 0 .../default/cb/0.1.0/sampbook/metadata.rb | 3 + .../0.1.0/sampbook}/recipes/default.rb | 0 .../files/default/cb/0.1.0/testbook/README.md | 1 + .../0.1.0/testbook}/metadata.rb | 0 .../cb/0.1.0/testbook/recipes/default.rb | 1 + .../files/default/cb/0.2.0/sampbook/README.md | 1 + .../default/cb/0.2.0/sampbook/metadata.rb | 3 + .../cb/0.2.0/sampbook/recipes/default.rb | 1 + .../files/default/cb/0.2.0/testbook/README.md | 1 + .../default/cb/0.2.0/testbook/metadata.rb | 3 + .../cb/0.2.0/testbook/recipes/default.rb | 1 + .../default/go/src/chefapi_test/bin/cookbook | 10 ++ .../src/chefapi_test/cmd/cookbook/cookbook.go | 120 +++++++++++------- .../go/src/chefapi_test/cmd/node/nodes.go | 1 - .../default/test_book/attributes/default.rb | 1 - .../default/test_book/files/default/testfile | 1 - .../default/test_book/templates/test.erb | 1 - .../chefapi_examples/recipes/chef_objects.rb | 12 +- .../default/inspec/cookbook_spec.rb | 17 +++ 21 files changed, 132 insertions(+), 54 deletions(-) rename examples/chefapi_examples/files/default/{test_book => cb/0.1.0/sampbook}/README.md (100%) create mode 100644 examples/chefapi_examples/files/default/cb/0.1.0/sampbook/metadata.rb rename examples/chefapi_examples/files/default/{test_book => cb/0.1.0/sampbook}/recipes/default.rb (100%) create mode 100644 examples/chefapi_examples/files/default/cb/0.1.0/testbook/README.md rename examples/chefapi_examples/files/default/{test_book => cb/0.1.0/testbook}/metadata.rb (100%) create mode 100644 examples/chefapi_examples/files/default/cb/0.1.0/testbook/recipes/default.rb create mode 100644 examples/chefapi_examples/files/default/cb/0.2.0/sampbook/README.md create mode 100644 examples/chefapi_examples/files/default/cb/0.2.0/sampbook/metadata.rb create mode 100644 examples/chefapi_examples/files/default/cb/0.2.0/sampbook/recipes/default.rb create mode 100644 examples/chefapi_examples/files/default/cb/0.2.0/testbook/README.md create mode 100644 examples/chefapi_examples/files/default/cb/0.2.0/testbook/metadata.rb create mode 100644 examples/chefapi_examples/files/default/cb/0.2.0/testbook/recipes/default.rb create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook delete mode 100644 examples/chefapi_examples/files/default/test_book/attributes/default.rb delete mode 100644 examples/chefapi_examples/files/default/test_book/files/default/testfile delete mode 100644 examples/chefapi_examples/files/default/test_book/templates/test.erb create mode 100644 examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb diff --git a/examples/README.md b/examples/README.md index 29477dd..6fa10e9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,11 @@ # Examples for using the api ## Coded samples +The code directory has examples of using the client code to exercise the chef api end points. +The bin directory has command for invoking the code. +The chefapi_examples cookbook creates a chef server instance. Then runs the code and verifies +the resulting output using inspec tests. + ## Cookbook and kitchen testing +Run kitchen converge to create a chef server instance in a linux image running under vagrant. +Run kitchen verify to code run the go examples that using the client api and to see confirm +that the results are as expected. diff --git a/examples/chefapi_examples/files/default/test_book/README.md b/examples/chefapi_examples/files/default/cb/0.1.0/sampbook/README.md similarity index 100% rename from examples/chefapi_examples/files/default/test_book/README.md rename to examples/chefapi_examples/files/default/cb/0.1.0/sampbook/README.md diff --git a/examples/chefapi_examples/files/default/cb/0.1.0/sampbook/metadata.rb b/examples/chefapi_examples/files/default/cb/0.1.0/sampbook/metadata.rb new file mode 100644 index 0000000..af8789b --- /dev/null +++ b/examples/chefapi_examples/files/default/cb/0.1.0/sampbook/metadata.rb @@ -0,0 +1,3 @@ +name 'sampbook' +description 'A test cookbook that does nothing' +version '0.1.0' diff --git a/examples/chefapi_examples/files/default/test_book/recipes/default.rb b/examples/chefapi_examples/files/default/cb/0.1.0/sampbook/recipes/default.rb similarity index 100% rename from examples/chefapi_examples/files/default/test_book/recipes/default.rb rename to examples/chefapi_examples/files/default/cb/0.1.0/sampbook/recipes/default.rb diff --git a/examples/chefapi_examples/files/default/cb/0.1.0/testbook/README.md b/examples/chefapi_examples/files/default/cb/0.1.0/testbook/README.md new file mode 100644 index 0000000..59d79a7 --- /dev/null +++ b/examples/chefapi_examples/files/default/cb/0.1.0/testbook/README.md @@ -0,0 +1 @@ +An example test cookbook diff --git a/examples/chefapi_examples/files/default/test_book/metadata.rb b/examples/chefapi_examples/files/default/cb/0.1.0/testbook/metadata.rb similarity index 100% rename from examples/chefapi_examples/files/default/test_book/metadata.rb rename to examples/chefapi_examples/files/default/cb/0.1.0/testbook/metadata.rb diff --git a/examples/chefapi_examples/files/default/cb/0.1.0/testbook/recipes/default.rb b/examples/chefapi_examples/files/default/cb/0.1.0/testbook/recipes/default.rb new file mode 100644 index 0000000..c452f47 --- /dev/null +++ b/examples/chefapi_examples/files/default/cb/0.1.0/testbook/recipes/default.rb @@ -0,0 +1 @@ +# default recipe diff --git a/examples/chefapi_examples/files/default/cb/0.2.0/sampbook/README.md b/examples/chefapi_examples/files/default/cb/0.2.0/sampbook/README.md new file mode 100644 index 0000000..59d79a7 --- /dev/null +++ b/examples/chefapi_examples/files/default/cb/0.2.0/sampbook/README.md @@ -0,0 +1 @@ +An example test cookbook diff --git a/examples/chefapi_examples/files/default/cb/0.2.0/sampbook/metadata.rb b/examples/chefapi_examples/files/default/cb/0.2.0/sampbook/metadata.rb new file mode 100644 index 0000000..b45d45f --- /dev/null +++ b/examples/chefapi_examples/files/default/cb/0.2.0/sampbook/metadata.rb @@ -0,0 +1,3 @@ +name 'sampbook' +description 'A test cookbook that does nothing' +version '0.2.0' diff --git a/examples/chefapi_examples/files/default/cb/0.2.0/sampbook/recipes/default.rb b/examples/chefapi_examples/files/default/cb/0.2.0/sampbook/recipes/default.rb new file mode 100644 index 0000000..c452f47 --- /dev/null +++ b/examples/chefapi_examples/files/default/cb/0.2.0/sampbook/recipes/default.rb @@ -0,0 +1 @@ +# default recipe diff --git a/examples/chefapi_examples/files/default/cb/0.2.0/testbook/README.md b/examples/chefapi_examples/files/default/cb/0.2.0/testbook/README.md new file mode 100644 index 0000000..59d79a7 --- /dev/null +++ b/examples/chefapi_examples/files/default/cb/0.2.0/testbook/README.md @@ -0,0 +1 @@ +An example test cookbook diff --git a/examples/chefapi_examples/files/default/cb/0.2.0/testbook/metadata.rb b/examples/chefapi_examples/files/default/cb/0.2.0/testbook/metadata.rb new file mode 100644 index 0000000..0a2b110 --- /dev/null +++ b/examples/chefapi_examples/files/default/cb/0.2.0/testbook/metadata.rb @@ -0,0 +1,3 @@ +name 'testbook' +description 'A test cookbook that does nothing' +version '0.2.0' diff --git a/examples/chefapi_examples/files/default/cb/0.2.0/testbook/recipes/default.rb b/examples/chefapi_examples/files/default/cb/0.2.0/testbook/recipes/default.rb new file mode 100644 index 0000000..c452f47 --- /dev/null +++ b/examples/chefapi_examples/files/default/cb/0.2.0/testbook/recipes/default.rb @@ -0,0 +1 @@ +# default recipe diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook new file mode 100755 index 0000000..25ef0bd --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook @@ -0,0 +1,10 @@ +#!/bin/bash + +# Cookbook testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds + +go run ${BASE}/../cmd/cookbook/cookbook.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go index 1fe4e85..e6e801f 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go @@ -5,73 +5,97 @@ package main import ( "fmt" - "io/ioutil" "os" - chef "github.com/go-chef/chef" - //chef "github.com/MarkGibbons/chefapi" + // chef "github.com/go-chef/chef" + "chefapi_test/testapi" ) // main Exercise the chef server api func main() { // Create a client for user access - client := buildClient(user, keyfile, chefurl) + client := testapi.Client() - // Cookbooks - fmt.Println("") - fmt.Println("Starting cookbooks") - cookbookList := listCookbooks(client) - fmt.Println(cookbookList)) + // Prep by adding a couple versions of some cookbooks before running this code + // testbook version 0.1.0 and 0.2.0 + // sampbook version 0.1.0 and 0.2.0 - // cookbooks GET - // List cookbooks + fmt.Println("Starting cookbooks") + // Cookbooks cookList, err := client.Cookbooks.List() if err != nil { - fmt.Fprintln(os.STDERR, "Issue listing cookbooks:", err) + fmt.Fprintln(os.Stderr, "Issue listing cookbooks:", err) } - // cookbooks/_laters GET - // cookbooks/_recipes GET - // cookbooks/NAME GET - // cookbooks/NAME/VERSION DELETE, GET, PUT + fmt.Printf("List initial cookbooks %+v\nEndInitialList\n", cookList) -} + // cookbook GET info + testbook, err := client.Cookbooks.Get("testbook") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue getting cookbook testbook:", err) + } + fmt.Printf("Get cookbook testbook %+v\n", testbook) -// buildClient creates a connection to a chef server using the chef api. -func buildClient(user string, keyfile string, baseurl string) *chef.Client { - key := clientKey(keyfile) - client, err := chef.NewClient(&chef.Config{ - Name: user, - Key: string(key), - BaseURL: baseurl, - // goiardi is on port 4545 by default, chef-zero is 8889, chef-server is on 443 - }) + // GET missing cookbook + nothere, err := client.Cookbooks.Get("nothere") if err != nil { - fmt.Println("Issue setting up client:", err) - os.Exit(1) - } - return client -} + fmt.Fprintln(os.Stderr, "Issue getting cookbook nothere:", err) + } + fmt.Printf("Get cookbook nothere %+v\n", nothere) -// clientKey reads the pem file containing the credentials needed to use the chef client. -func clientKey(filepath string) string { - key, err := ioutil.ReadFile(filepath) + // list available versions of a cookbook + testbookversions, err := client.Cookbooks.GetAvailableVersions("testbook", "0") if err != nil { - fmt.Println("Couldn't read key.pem:", err) - os.Exit(1) - } - return string(key) -} + fmt.Fprintln(os.Stderr, "Issue getting cookbook versions for testbook:", err) + } + fmt.Printf("Get cookbook versions testbook %+v\n", testbookversions) -// listCookbooks uses the chef server api to list all cookbooks -func listCookbooks(client *chef.Client, filters ...string) map[string]string { - var filter string - if len(filters) > 0 { - filter = filters[0] + // list available versions of a cookbook + sampbookversions, err := client.Cookbooks.GetAvailableVersions("sampbook", "0") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue getting cookbook versions for sampbook:", err) } - userList, err := client.Users.List(filter) + fmt.Printf("Get cookbook versions sampbook %+v\n", sampbookversions) + + // get specific versions of a cookbook + testbookversions1, err := client.Cookbooks.GetVersion("testbook", "0.1.0") if err != nil { - fmt.Println("Issue listing users:", err) - } - return userList + fmt.Fprintln(os.Stderr, "Issue getting specific cookbook versions for testbook:", err) + } + fmt.Printf("Get specific cookbook version testbook %+v\n", testbookversions1) + + // list all recipes + allRecipes, err := client.Cookbooks.ListAllRecipes() + if err != nil { + fmt.Fprintln(os.Stderr, "Issue getting all recipes:", err) + } + fmt.Printf("Get all recipes %+v\n", allRecipes) + + // delete version + err = client.Cookbooks.Delete("testbook", "0.1.0") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting testbook 0.1.0:", err) + } + fmt.Printf("Delete testbook 0.1.0 %+v\n", err) + + // delete version + err = client.Cookbooks.Delete("testbook", "0.2.0") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting testbook 0.2.0:", err) + } + fmt.Printf("Delete testbook 0.2.0 %+v\n", err) + + // List cookbooks + cookList, err = client.Cookbooks.List() + if err != nil { + fmt.Fprintln(os.Stderr, "Issue listing cookbooks:", err) + } + fmt.Printf("Final cookbook list %+v\n", cookList) + + // list available versions of a cookbook + sampbookversions, err = client.Cookbooks.GetAvailableVersions("sampbook", "0") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue getting cookbook versions for sampbook:", err) + } + fmt.Printf("Final cookbook versions sampbook %+v\n", sampbookversions) } diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go index c94cf5b..c0c53fc 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go @@ -11,7 +11,6 @@ import ( func main() { // Use the default test org client := testapi.Client() - fmt.Println("Client", client) // List initial nodes nodeList, err := client.Nodes.List() diff --git a/examples/chefapi_examples/files/default/test_book/attributes/default.rb b/examples/chefapi_examples/files/default/test_book/attributes/default.rb deleted file mode 100644 index 2b902d4..0000000 --- a/examples/chefapi_examples/files/default/test_book/attributes/default.rb +++ /dev/null @@ -1 +0,0 @@ -# default attributes file diff --git a/examples/chefapi_examples/files/default/test_book/files/default/testfile b/examples/chefapi_examples/files/default/test_book/files/default/testfile deleted file mode 100644 index 9c1732b..0000000 --- a/examples/chefapi_examples/files/default/test_book/files/default/testfile +++ /dev/null @@ -1 +0,0 @@ -# sample test file diff --git a/examples/chefapi_examples/files/default/test_book/templates/test.erb b/examples/chefapi_examples/files/default/test_book/templates/test.erb deleted file mode 100644 index 6891ffd..0000000 --- a/examples/chefapi_examples/files/default/test_book/templates/test.erb +++ /dev/null @@ -1 +0,0 @@ -# Sample template diff --git a/examples/chefapi_examples/recipes/chef_objects.rb b/examples/chefapi_examples/recipes/chef_objects.rb index 7bd0816..f1aaffb 100644 --- a/examples/chefapi_examples/recipes/chef_objects.rb +++ b/examples/chefapi_examples/recipes/chef_objects.rb @@ -51,8 +51,16 @@ mode '0755' end -remote_directory '/fixtures/chef/cookbooks/test_book' do - source 'test_book' +remote_directory '/fixtures/chef/cb' do + source 'cb' +end + +# Cookbook upload all 4 books +bash 'upload testbook 0.1.0' do + code <<-EOH + knife cookbook upload sampbook testbook -s https://testhost/organizations/test -u pivotal -k /etc/opscode/pivotal.pem -o /fixtures/chef/cb/0.1.0 + knife cookbook upload sampbook testbook -s https://testhost/organizations/test -u pivotal -k /etc/opscode/pivotal.pem -o /fixtures/chef/cb/0.2.0 +EOH end directory '/var/log/chef' do diff --git a/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb new file mode 100644 index 0000000..51cd631 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb @@ -0,0 +1,17 @@ +# Inspec tests for the cookbook chef api go module +# +describe command('/go/src/chefapi_test/bin/cookbook') do + its('stderr') { should match(%r{^Issue getting cookbook nothere: GET https://localhost/organizations/test/cookbooks/nothere: 404}) } + its('stderr') { should_not match(/testbook/) } + its('stderr') { should_not match(/sampbook/) } + its('stdout') { should match(%r{^List initial cookbooks (?=.*sampbook => https://localhost/organizations/test/cookbooks/sampbook\n\s*\* 0.2.0)(?=.*testbook => https://localhost/organizations/test/cookbooks/testbook\n\s*\* 0.2.0).*EndInitialList}m) } + # output from get cookbook is odd + its('stdout') { should match(/^Get cookbook testbook/) } + its('stdout') { should match(%r{^Get cookbook versions testbook testbook => https://localhost/organizations/test/cookbooks/testbook\n\s*\* 0.2.0\n\s*\* 0.1.0}m) } + its('stdout') { should match(%r{^Get cookbook versions sampbook sampbook => https://localhost/organizations/test/cookbooks/sampbook\n\s*\* 0.2.0\n\s*\* 0.1.0}m) } + its('stdout') { should match(/^Get specific cookbook version testbook {CookbookName:testbook/) } + its('stdout') { should match(/^Get all recipes \[sampbook testbook\]/) } + its('stdout') { should match(/^Delete testbook 0.1.0 /) } + its('stdout') { should match(%r{^Final cookbook list sampbook => https://localhost/organizations/test/cookbooks/sampbook\n\s*\* 0.2.0}m) } + its('stdout') { should match(%r{^Final cookbook versions sampbook sampbook => https://localhost/organizations/test/cookbooks/sampbook\n\s*\* 0.2.0\n\s*\* 0.1.0}m) } +end From 88342fc503232d2aea0df6244b65cf21ade7289f Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 28 Dec 2019 16:12:05 -0800 Subject: [PATCH 21/65] Upload the test cookbooks from within the cookbook.go test cmd --- examples/chefapi_examples/TODO.md | 4 ++-- .../src/chefapi_test/cmd/cookbook/cookbook.go | 18 ++++++++++++++++++ .../chefapi_examples/recipes/chef_objects.rb | 12 ++---------- .../default/inspec/cookbook_spec.rb | 1 + 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/examples/chefapi_examples/TODO.md b/examples/chefapi_examples/TODO.md index ac59edc..2228c54 100644 --- a/examples/chefapi_examples/TODO.md +++ b/examples/chefapi_examples/TODO.md @@ -1,2 +1,2 @@ -* Allow for testing the local go-chef/chef version -* Flag for ssl verify +* Allow for testing the local go-chef/chef verion +* Flag for ssl verify, figure out how not to turn of ssl authentication diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go index e6e801f..c128cbd 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go @@ -6,6 +6,7 @@ package main import ( "fmt" "os" + "os/exec" // chef "github.com/go-chef/chef" "chefapi_test/testapi" @@ -18,6 +19,7 @@ func main() { client := testapi.Client() // Prep by adding a couple versions of some cookbooks before running this code + err := addSampleCookbooks() // testbook version 0.1.0 and 0.2.0 // sampbook version 0.1.0 and 0.2.0 @@ -99,3 +101,19 @@ func main() { } fmt.Printf("Final cookbook versions sampbook %+v\n", sampbookversions) } + +func addSampleCookbooks() (err error) { + cmd := exec.Command("knife", "cookbook", "upload", "sampbook", "testbook", "-s", "https://testhost/organizations/test", "-u", "pivotal", "-k", "/etc/opscode/pivotal.pem", "-o", "/fixtures/chef/cb/0.1.0") + out, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(os.Stderr, "Issue loading cookbook:", err) + } + fmt.Printf("Load 0.1.0 cookbook versions: %+v", string(out)) + cmd = exec.Command("knife", "cookbook", "upload", "sampbook", "testbook", "-s", "https://testhost/organizations/test", "-u", "pivotal", "-k", "/etc/opscode/pivotal.pem", "-o", "/fixtures/chef/cb/0.2.0") + out, err = cmd.CombinedOutput() + if err != nil { + fmt.Println(os.Stderr, "Issue loading cookbook:", err) + } + fmt.Printf("Load 0.2.0 cookbook versions: %+v", string(out)) + return +} diff --git a/examples/chefapi_examples/recipes/chef_objects.rb b/examples/chefapi_examples/recipes/chef_objects.rb index f1aaffb..a30debc 100644 --- a/examples/chefapi_examples/recipes/chef_objects.rb +++ b/examples/chefapi_examples/recipes/chef_objects.rb @@ -1,4 +1,4 @@ -# Add chef objects to the server for testing +recipes/chef_objects.rb# Add chef objects to the server for testing execute 'Set the host name' do command 'hostname testhost' @@ -55,18 +55,10 @@ source 'cb' end -# Cookbook upload all 4 books -bash 'upload testbook 0.1.0' do - code <<-EOH - knife cookbook upload sampbook testbook -s https://testhost/organizations/test -u pivotal -k /etc/opscode/pivotal.pem -o /fixtures/chef/cb/0.1.0 - knife cookbook upload sampbook testbook -s https://testhost/organizations/test -u pivotal -k /etc/opscode/pivotal.pem -o /fixtures/chef/cb/0.2.0 -EOH -end - directory '/var/log/chef' do recursive true end directory '/var/chef' do - recursive true +recipes/chef_objects.rb recursive true end diff --git a/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb index 51cd631..7828f09 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb @@ -4,6 +4,7 @@ its('stderr') { should match(%r{^Issue getting cookbook nothere: GET https://localhost/organizations/test/cookbooks/nothere: 404}) } its('stderr') { should_not match(/testbook/) } its('stderr') { should_not match(/sampbook/) } + its('stderr') { should_not match(/Issue loading/) } its('stdout') { should match(%r{^List initial cookbooks (?=.*sampbook => https://localhost/organizations/test/cookbooks/sampbook\n\s*\* 0.2.0)(?=.*testbook => https://localhost/organizations/test/cookbooks/testbook\n\s*\* 0.2.0).*EndInitialList}m) } # output from get cookbook is odd its('stdout') { should match(/^Get cookbook testbook/) } From 63477309c8c31885fc8c7c42d14e867c083dcb3b Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 29 Dec 2019 12:04:34 -0800 Subject: [PATCH 22/65] Add examples for using group.go and tests --- examples/chefapi_examples/TODO.md | 6 + .../default/go/src/chefapi_test/bin/group | 9 + .../go/src/chefapi_test/cmd/group/group.go | 196 ++++-------------- .../chefapi_examples/recipes/chef_objects.rb | 4 +- .../integration/default/inspec/group_spec.rb | 13 ++ 5 files changed, 75 insertions(+), 153 deletions(-) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group create mode 100644 examples/chefapi_examples/test/integration/default/inspec/group_spec.rb diff --git a/examples/chefapi_examples/TODO.md b/examples/chefapi_examples/TODO.md index 2228c54..674e498 100644 --- a/examples/chefapi_examples/TODO.md +++ b/examples/chefapi_examples/TODO.md @@ -1,2 +1,8 @@ * Allow for testing the local go-chef/chef verion * Flag for ssl verify, figure out how not to turn of ssl authentication + +* live testing +* grou +* search +* sandbox +* user diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group new file mode 100755 index 0000000..b34af11 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group @@ -0,0 +1,9 @@ +#!/bin/bash + +# Group testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds +go run ${BASE}/../cmd/group/group.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/group/group.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/group/group.go index 1263b0f..53157c8 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/group/group.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/group/group.go @@ -1,191 +1,85 @@ // -// Test the go-chef/chef chef server api /organizations endpoints against a live server +// Test the go-chef/chef chef server api /group endpoints against a live server // package main import ( "fmt" - "io/ioutil" + "chefapi_test/testapi" + "github.com/go-chef/chef" "os" - "strconv" - - chef "github.com/go-chef/chef" ) // main Exercise the chef server api func main() { - // Pass in the database and chef-server api credentials. - org1 := "org1" - org2 := "org2" - user := os.Args[1] - keyfile := os.Args[2] - chefurl := os.Args[3] - skipssl, err := strconv.ParseBool(os.Args[4]) - if err != nil { - skipssl = true - } - - // Create a client for access - client := buildClient(user, keyfile, chefurl, skipssl) - - // Organization tests - orgList := listOrganizations(client) - fmt.Println("List initial organizations", orglistorganization.go) - - orgResult := createOrganization(client, chef.Organization{Name: "org1", FullName: "organization1"}) - fmt.Println("Added org1", orgResult) - - orgResult = createOrganization(client, chef.Organization{Name: "org1", FullName: "organization1"}) - fmt.Println("Added org1 again", orgResult) - - orgResult = createOrganization(client, chef.Organization{Name: "org2", FullName: "organization2"}) - fmt.Println("Added org2", orgResult) - - orgout := getOrganization(client, org1) - fmt.Println("Get org1", orgout) - - orgList = listOrganizations(client) - fmt.Println("List organizations After adding org1 and org2", orgList) - - orgresult := updateOrganization(client, chef.Organization{Name: "org1", FullName: "new_organization1"}) - fmt.Println("Update org1", orgresult) - - orgout = getOrganization(client, org1) - fmt.Println("Get org1 after update", orgout) - - orgErr := deleteOrganization(client, org2) - fmt.Println("Delete org2", orgErr) - - orgList = listOrganizations(client) - fmt.Println("List organizations after deleting org2", orgList) - - // Clean up - orgErr = deleteOrganization(client, org1) - fmt.Println("Result from deleting org1", orgErr) - - orgList = listOrganizations(client) - fmt.Println("List organizations after cleanup", orgList) - -} + client := testapi.Client() -// buildClient creates a connection to a chef server using the chef api. -func buildClient(user string, keyfile string, baseurl string, skipssl bool) *chef.Client { - key := clientKey(keyfile) - client, err := chef.NewClient(&chef.Config{ - Name: user, - Key: string(key), - BaseURL: baseurl, - SkipSSL: skipssl, - // goiardi is on port 4545 by default, chef-zero is 8889, chef-server is on 443 - }) + // List the current groups + groupList, err := client.Groups.List() if err != nil { - fmt.Println("Issue setting up client:", err) - os.Exit(1) + fmt.Fprintln(os.Stderr, "Issue printing the existing groups:", err) } - return client -} + fmt.Printf("List initial groups %+vEndInitialList\n", groupList) -// clientKey reads the pem file containing the credentials needed to use the chef client. -func clientKey(filepath string) string { - key, err := ioutil.ReadFile(filepath) - if err != nil { - fmt.Println("Couldn't read key.pem:", err) - os.Exit(1) + // Build a stucture to define a group + group1 := chef.Group { + Name: "group1", + GroupName: "group1", } - return string(key) -} -// createOrganization uses the chef server api to create a single organization -func createOrganization(client *chef.Client, org chef.Organization) chef.OrganizationResult { - orgResult, err := client.Organizations.Create(org) + // Add a new group + groupAdd, err := client.Groups.Create(group1) if err != nil { - fmt.Println("Issue creating org:", err) + fmt.Fprintln(os.Stderr, "Issue adding group1:", err) } - return orgResult -} + fmt.Println("Added group1", groupAdd) -// deleteOrganization uses the chef server api to delete a single organization -func deleteOrganization(client *chef.Client, name string) error { - err := client.Organizations.Delete(name) + // Add group again + groupAdd, err = client.Groups.Create(group1) if err != nil { - fmt.Println("Issue deleting org:", err) + fmt.Fprintln(os.Stderr, "Issue recreating group1:", err) } - return err -} + fmt.Println("Recreated group1", groupAdd) -// getOrganization uses the chef server api to get information for a single organization -func getOrganization(client *chef.Client, name string) chef.Organization { - // todo: everything - orgList, err := client.Organizations.Get(name) + // List groups after adding + groupList, err = client.Groups.List() if err != nil { - fmt.Println("Issue listing orgs:", err) + fmt.Fprintln(os.Stderr, "Issue printing the existing groups:", err) } - return orgList -} + fmt.Printf("List groups after adding group1 %+vEndAddList\n", groupList) -// listOrganizations uses the chef server api to list all organizations -func listOrganizations(client *chef.Client) map[string]string { - orgList, err := client.Organizations.List() + // Get new group + groupOut, err := client.Groups.Get("group1") if err != nil { - fmt.Println("Issue listing orgs:", err) + fmt.Fprintln(os.Stderr, "Issue getting group1:", err) } - return orgList -} + fmt.Printf("Get group1 %+v\n", groupOut) -// updateOrganization uses the chef server api to update information for a single organization -func updateOrganization(client *chef.Client, org chef.Organization) chef.Organization { - org_update, err := client.Organizations.Update(org) + // Try to get a missing group + groupOutMissing, err := client.Groups.Get("nothere") if err != nil { - fmt.Println("Issue updating org:", err) + fmt.Fprintln(os.Stderr, "Issue getting nothere:", err) } - return org_update -} + fmt.Println("Get nothere", groupOutMissing) -/* -// orgGroups gets a list of groups, from the chef server, belonging to an organization. -func orgGroups(client *chef.Client, org string) map[string]string { - groupList, err := client.Groups.List() + // Update a group + group1.GroupName = "group1-new" + group1.Users = append(group1.Users, "pivotal") + groupUpdate, err := client.Groups.Update(group1) if err != nil { - fmt.Println("Issue listing groups:", err) - panic(err.Error()) // proper error handling instead of panic in your app + fmt.Fprintln(os.Stderr, "Issue updating group1:", err) } - return groupList -} + fmt.Printf("Update group1 %+v\n", groupUpdate) -// getGroup gets group information from the chef server. The -// members of the group and nested groups are retrieved. -func getGroup(client *chef.Client, group string) chef.Group { - groupInfo, err := client.Groups.Get(group) - if err != nil { - fmt.Println("Issue getting: "+group, err) - panic(err.Error()) // proper error handling instead of panic in your app - } - return groupInfo -} + // Clean up + err = client.Groups.Delete("group1-new") + fmt.Println("Delete group1", err) -// getMember gets the information associated with a particular user account. -func getMember(client *chef.Client, member string) chef.User { - memberInfo, err := client.Users.Get(member) + // Final list of groups + groupList, err = client.Groups.List() if err != nil { - fmt.Println("Issue getting: "+member, err) - panic(err.Error()) // proper error handling instead of panic in your app - } - return memberInfo -} - -// usersFromGroups gets the nested groups. getGroupMembers and userFromGroups -// call each other in a recursive fashion to expand the nested groups -func usersFromGroups(client *chef.Client, groups []string) []string { - var members []string - for _, group := range groups { - groupInfo, err := client.Groups.Get(group) - if err != nil { - fmt.Println("Issue with regex", err) - panic(err.Error()) // proper error handling instead of panic in your app - } - members = getGroupMembers(client, groupInfo) + fmt.Fprintln(os.Stderr, "Issue listing the final groups:", err) } - return members + fmt.Printf("List groups after cleanup %+vEndFinalList\n", groupList) } -*/ diff --git a/examples/chefapi_examples/recipes/chef_objects.rb b/examples/chefapi_examples/recipes/chef_objects.rb index a30debc..847e366 100644 --- a/examples/chefapi_examples/recipes/chef_objects.rb +++ b/examples/chefapi_examples/recipes/chef_objects.rb @@ -1,4 +1,4 @@ -recipes/chef_objects.rb# Add chef objects to the server for testing +# Add chef objects to the server for testing execute 'Set the host name' do command 'hostname testhost' @@ -60,5 +60,5 @@ end directory '/var/chef' do -recipes/chef_objects.rb recursive true + recursive true end diff --git a/examples/chefapi_examples/test/integration/default/inspec/group_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/group_spec.rb new file mode 100644 index 0000000..6817ec1 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/group_spec.rb @@ -0,0 +1,13 @@ +# Inspec tests for the group chef api go module +# + +describe command('/go/src/chefapi_test/bin/group') do + its('stderr') { should match(%r{^Issue recreating group1. POST https://localhost/organizations/test/groups: 409}) } + its('stdout') { should match(%r{^List initial groups map\[(?=.*admins:https://localhost/organizations/test/groups/admins)(?=.*billing-admins:https://localhost/organizations/test/groups/billing-admins)(?=.*clients:https://localhost/organizations/test/groups/clients)(?=.*users:https://localhost/organizations/test/groups/users)(?=.*public_key_read_access:https://localhost/organizations/test/groups/public_key_read_access).*\]EndInitialList}) } + its('stdout') { should match(%r{^Added group1 \&\{https://localhost/organizations/test/groups/group1\}}) } + its('stdout') { should match(%r{^List groups after adding group1 map\[(?=.*group1:https://localhost/organizations/test/groups/group1)(?=.*admins:https://localhost/organizations/test/groups/admins)(?=.*billing-admins:https://localhost/organizations/test/groups/billing-admins)(?=.*clients:https://localhost/organizations/test/groups/clients)(?=.*users:https://localhost/organizations/test/groups/users)(?=.*public_key_read_access:https://localhost/organizations/test/groups/public_key_read_access).*\]EndAddList}) } + its('stdout') { should match(/^Get group1 \{Name:group1 GroupName:group1 OrgName:test Actors:\[\] Clients:\[\] Groups:\[\] Users:\[\]\}/) } + its('stdout') { should match(/^Update group1 \{Name:group1 GroupName:group1-new OrgName: Actors:\[\] Clients:\[\] Groups:\[\] Users:\[pivotal\]\}/) } + its('stdout') { should match(/^Delete group1 /) } + its('stdout') { should match(%r{^List groups after cleanup map\[(?=.*admins:https://localhost/organizations/test/groups/admins)(?=.*billing-admins:https://localhost/organizations/test/groups/billing-admins)(?=.*clients:https://localhost/organizations/test/groups/clients)(?=.*users:https://localhost/organizations/test/groups/users)(?=.*public_key_read_access:https://localhost/organizations/test/groups/public_key_read_access).*\]EndFinalList}) } +end From bb38d7563c6af97942d35326b5c4c2ddbfb00756 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 29 Dec 2019 12:16:32 -0800 Subject: [PATCH 23/65] Try to clean up the circleci checks --- .circleci/config.yml | 6 +++--- release.go | 5 ----- search.go | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) delete mode 100644 release.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 26aea15..b7ebab7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,9 +40,9 @@ jobs: - 35:v45:41:ba:cf:3f:7f:d5:00:0f:11:6b:4d:c0:a1:90 - checkout # specify any bash command here prefixed with `run: ` - - run: go get -v ./... + - run: go get -v *.go - run: go get github.com/ctdk/goiardi/chefcrypto - run: go get github.com/smartystreets/goconvey/convey - run: go get github.com/stretchr/testify/assert - - run: go vet ./... - - run: go test ./... + - run: go vet *.go + - run: go test *.go diff --git a/release.go b/release.go deleted file mode 100644 index 34ac92d..0000000 --- a/release.go +++ /dev/null @@ -1,5 +0,0 @@ -// +build !debug - -package chef - -func debug(fmt string, args ...interface{}) {} diff --git a/search.go b/search.go index 914b0a6..3cb0a03 100644 --- a/search.go +++ b/search.go @@ -53,7 +53,7 @@ func (q SearchQuery) DoPartial(client *Client, params map[string]interface{}) (r body, err := JSONReader(params) if err != nil { - debug("Problem encoding params for body", err.Error()) + debug("Problem encoding params for body %v", err.Error()) return } From 9d928aa65d50afc8354175c042b05046349a2991 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 29 Dec 2019 21:58:41 -0800 Subject: [PATCH 24/65] Add examples and tests for the user.go code --- examples/chefapi_examples/TODO.md | 7 +- .../default/go/src/chefapi_test/bin/user | 9 + .../go/src/chefapi_test/cmd/user/user.go | 218 ++++++------------ .../integration/default/inspec/user_spec.rb | 18 ++ 4 files changed, 97 insertions(+), 155 deletions(-) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user create mode 100644 examples/chefapi_examples/test/integration/default/inspec/user_spec.rb diff --git a/examples/chefapi_examples/TODO.md b/examples/chefapi_examples/TODO.md index 674e498..f2dec02 100644 --- a/examples/chefapi_examples/TODO.md +++ b/examples/chefapi_examples/TODO.md @@ -2,7 +2,10 @@ * Flag for ssl verify, figure out how not to turn of ssl authentication * live testing -* grou * search * sandbox -* user + +Missing api stuff +* user authentication +* user update +* user verbose output diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user new file mode 100755 index 0000000..26a58da --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user @@ -0,0 +1,9 @@ +#!/bin/bash + +# Group testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds +go run ${BASE}/../cmd/user/user.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go index c8ca1f8..b731540 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go @@ -5,171 +5,82 @@ package main import ( "fmt" - "io/ioutil" - "os" - // "github.com/go-chef/chef" - chef "github.com/MarkGibbons/chefapi" + "github.com/go-chef/chef" + "chefapi_test/testapi" + "os" ) // main Exercise the chef server api func main() { - // Pass in the database and chef-server api credentials. - usr1 := "usr1" - usr2 := "usr2" - user := os.Args[1] - keyfile := os.Args[2] - chefurl := os.Args[3] + client := testapi.Client() - // Create a client for user access - client := buildClient(user, keyfile, chefurl) + var usr1 chef.User + usr1 = chef.User{ UserName: "usr1", + Email: "user1@domain.io", + FirstName: "user1", + LastName: "fullname", + DisplayName: "User1 Fullname", + Password: "Logn12ComplexPwd#", + } // Users - fmt.Println("") - fmt.Println("Starting users") - userList := listUsers(client) // map[string]string a1px:https://chefp01.nordstrom.net/users/a1px - fmt.Println(userList) - - fmt.Println("") - fmt.Println("Added usr1") - userResult := createUser(client, chef.User{ UserName: usr1, DisplayName: "Show Me", Email: "user1@domain.io", FirstName: "user1", FullName: "All the Name", LastName: "fullname", MiddleName: "J", Password: "Logn12ComplexPwd#" }) - fmt.Println(userResult) - - fmt.Println("") - fmt.Println("Filter users email=user1") - userList = listUsers(client, "email=user1@domain.io") // map[string]string a1px:https://chefp01.nordstrom.net/users/a1px - fmt.Println(userList) - - fmt.Println("") - fmt.Println("Verbose users") - userVerboseOut := listUsersVerbose(client) // []UserVerbose - fmt.Printf("Verbose Out %v\n", userVerboseOut) - - err := client.AuthenticateUser.Authenticate(chef.AuthenticateUser{ UserName: usr1, Password: "Logn12ComplexPwd#" }) - fmt.Println("") - fmt.Println("") - fmt.Println("Error returned from authenticate: ", err) - - fmt.Println("") - fmt.Println("Added usr1 again") - userResult = createUser(client, chef.User{ UserName: usr1, Email: "user1@domain.io", FirstName: "user1", LastName: "fullname", DisplayName: "User1 Fullname", Password: "mary" }) - fmt.Println(userResult) - - fmt.Println("") - fmt.Println("Added usr2") - userResult = createUser(client, chef.User{ UserName: usr2, Email: "user2@domain.io", FirstName: "User2", LastName: "Fullname", DisplayName: "User2 Fullname", ExternalAuthenticationUid: "mary" }) - fmt.Println(userResult) - - fmt.Println("") - fmt.Println("Filter users external_authentication_uid=mary") - userList = listUsers(client, "external_authentication_uid=mary") - fmt.Println(userList) - - fmt.Println("") - fmt.Println("Original usr1") - userout := getUser(client, usr1) - fmt.Println(userout) - - fmt.Println("") - fmt.Println("Original pivotal") + userList := listUsers(client) + fmt.Printf("List initial users %+v EndInitialList\n", userList) + + userResult := createUser(client, usr1) + fmt.Println("Add usr1", userResult) + + userList = listUsers(client, "email=user1@domain.io") + fmt.Printf("Filter users %+v\n", userList) + + // userVerboseOut := listUsersVerbose(client) + // fmt.Printf("Verbose out %v\n", userVerboseOut) + + // err := client.AuthenticateUser.Authenticate(chef.AuthenticateUser{ UserName: usr1, Password: "Logn12ComplexPwd#" }) + // fmt.Println("Authenticate usr1 ", err) + + userResult = createUser(client, usr1) + fmt.Printf("Add user1 again %+v\n", userResult) + + userout := getUser(client, "usr1") + fmt.Printf("Get usr1 %+v\n", userout) + userout = getUser(client, "pivotal") - fmt.Println(userout) + fmt.Printf("Pivotal user %+v\n", userout) - fmt.Println("") - fmt.Println("After adding usr1 and usr2") - userList = listUsers(client) - fmt.Println(userList) - - fmt.Println("") - fmt.Println("After updating usr1") - userbody := chef.User{ FullName: "usr1new" } - fmt.Println("User request", userbody) - userresult := updateUser(client, "usr1", userbody) - fmt.Println(userresult) - - fmt.Println("") - fmt.Println("Get usr1") - userout = getUser(client, usr1) - fmt.Println(userout) - - fmt.Println("") - fmt.Println("Delete usr2") - userd, userErr := deleteUser(client, usr2) - fmt.Println(userErr) - fmt.Println("delete data", userd) - - fmt.Println("") - fmt.Println("Delete usr1") - userd, userErr = deleteUser(client, usr1) - fmt.Println(userErr) - fmt.Println("delete data", userd) - - fmt.Println("") - fmt.Println("list after deleting users") userList = listUsers(client) - fmt.Println(userList) - -} + fmt.Printf("List after adding %+v EndAddList\n", userList) -// buildClient creates a connection to a chef server using the chef api. -func buildClient(user string, keyfile string, baseurl string) *chef.Client { - key := clientKey(keyfile) - client, err := chef.NewClient(&chef.Config{ - Name: user, - Key: string(key), - BaseURL: baseurl, - // goiardi is on port 4545 by default, chef-zero is 8889, chef-server is on 443 - }) - if err != nil { - fmt.Println("Issue setting up client:", err) - os.Exit(1) - } - return client -} + // userbody := chef.User{ FullName: "usr1new" } + // userresult := updateUser(client, "usr1", userbody) + // fmt.Printf("Update user1 %+v", userresult) -// clientKey reads the pem file containing the credentials needed to use the chef client. -func clientKey(filepath string) string { - key, err := ioutil.ReadFile(filepath) - if err != nil { - fmt.Println("Couldn't read key.pem:", err) - os.Exit(1) - } - return string(key) -} + // userout = getUser(client, "usr1") + // fmt.Println("Get usr1 after update %+v\n", userout) -// createOrganization uses the chef server api to create a single organization -func createOrganization(client *chef.Client, org chef.Organization) chef.OrganizationResult { - orgResult, err := client.Organizations.Create(org) - if err != nil { - fmt.Println("Issue creating org:", err) - } - return orgResult -} + err := deleteUser(client, "usr1") + fmt.Println("Delete usr1", err) -// deleteOrganization uses the chef server api to delete a single organization -func deleteOrganization(client *chef.Client, name string) error { - err := client.Organizations.Delete(name) - if err != nil { - fmt.Println("Issue deleting org:", err) - } - return err + userList = listUsers(client) + fmt.Printf("List after cleanup %+v EndCleanupList\n", userList) } // createUser uses the chef server api to create a single organization func createUser(client *chef.Client, user chef.User) chef.UserResult { usrResult, err := client.Users.Create(user) if err != nil { - fmt.Println("Issue creating user:", err) + fmt.Fprintln(os.Stderr, "Issue creating user:", err) } return usrResult } // deleteUser uses the chef server api to delete a single organization -func deleteUser(client *chef.Client, name string) (data chef.UserResult, err error) { +func deleteUser(client *chef.Client, name string) (err error) { err = client.Users.Delete(name) if err != nil { - fmt.Println("Issue deleting org:", err) + fmt.Fprintln(os.Stderr, "Issue deleting org:", err) } return } @@ -178,7 +89,7 @@ func deleteUser(client *chef.Client, name string) (data chef.UserResult, err err func getUser(client *chef.Client, name string) chef.User { userList, err := client.Users.Get(name) if err != nil { - fmt.Println("Issue listing user", err) + fmt.Fprintln(os.Stderr, "Issue listing user", err) } return userList } @@ -191,26 +102,27 @@ func listUsers(client *chef.Client, filters ...string) map[string]string { } userList, err := client.Users.List(filter) if err != nil { - fmt.Println("Issue listing users:", err) + fmt.Fprintln(os.Stderr, "Issue listing users:", err) } return userList } // listUsersVerbose uses the chef server api to list all users and return verbose output -func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseItem { - userList, err := client.Users.ListVerbose() - fmt.Println("VERBOSE LIST", userList) - if err != nil { - fmt.Println("Issue listing verbose users:", err) - } - return userList -} +//func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseItem { +// userList, err := client.Users.ListVerbose() + // fmt.Println("VERBOSE LIST", userList) +// if err != nil { +// fmt.Println("Issue listing verbose users:", err) +// } +// return userList +//} // updateUser uses the chef server api to update information for a single user -func updateUser(client *chef.Client, username string, user chef.User) chef.User { - user_update, err := client.Users.Update(username, user) - if err != nil { - fmt.Println("Issue updating user:", err) - } - return user_update -} +// func updateUser(client *chef.Client, username string, user chef.User) chef.User { +// user_update, err := client.Users.Update(username, user) + //if err != nil { + //i fmt.Println("Issue updating user:", err) + // +//} +// return user_update +//} diff --git a/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb new file mode 100644 index 0000000..3442131 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb @@ -0,0 +1,18 @@ +# Inspec tests for the user chef api go module +# + +describe command('/go/src/chefapi_test/bin/user') do + its('stderr') { should match(%r{^Issue creating user: POST https://localhost/users: 409}) } + its('stdout') { should match(%r{^List initial users map\[(?=.*pivotal:https://localhost/users/pivotal).*\] EndInitialList}) } + its('stdout') { should match(%r{^Add usr1 \{https://localhost/users/usr1 -----BEGIN RSA}) } + its('stdout') { should match(%r{^Filter users map\[usr1:https://localhost/users/usr1\]}) } + # its('stdout') { should match(%r{^Verbose out }) } + # its('stdout') { should match(%r{^Authenticate user1 }) } + its('stdout') { should match(/^Get usr1 \{UserName:usr1 DisplayName:User1 Fullname Email:user1@domain.io ExternalAuthenticationUid: FirstName:user1 FullName: LastName:fullname MiddleName: Password: PublicKey:-----BEGIN/) } + its('stdout') { should match(/^Pivotal user \{UserName:pivotal DisplayName:Chef Server Superuser Email:root@localhost.localdomain ExternalAuthenticationUid: FirstName:Chef FullName: LastName:Server MiddleName: Password: PublicKey:-----BEGIN/) } + its('stdout') { should match(%r{^List after adding map\[(?=.*pivotal:https://localhost/users/pivotal)(?=.*usr1:https://localhost/users/usr1).*\] EndAddList}) } + # its('stdout') { should match(/^Update usr1 /) } + # its('stdout') { should match(/^Get usr1 after update /) } + its('stdout') { should match(/^Delete usr1 /) } + its('stdout') { should match(%r{^List after cleanup map\[(?=.*pivotal:https://localhost/users/pivotal).*\] EndCleanupList}) } +end From 0c530a7e3211a59163cdbeb30fce83fde9f497ef Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 30 Dec 2019 12:05:09 -0800 Subject: [PATCH 25/65] Add examples and tests of the search module --- examples/chefapi_examples/TODO.md | 5 +- .../default/go/src/chefapi_test/bin/search | 9 ++ .../go/src/chefapi_test/cmd/search/search.go | 108 ++++++++++++++++++ .../go/src/chefapi_test/cmd/user/user.go | 1 - .../integration/default/inspec/search_spec.rb | 13 +++ search.go | 6 +- 6 files changed, 137 insertions(+), 5 deletions(-) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/search/search.go create mode 100644 examples/chefapi_examples/test/integration/default/inspec/search_spec.rb diff --git a/examples/chefapi_examples/TODO.md b/examples/chefapi_examples/TODO.md index f2dec02..e4194d9 100644 --- a/examples/chefapi_examples/TODO.md +++ b/examples/chefapi_examples/TODO.md @@ -1,11 +1,14 @@ * Allow for testing the local go-chef/chef verion * Flag for ssl verify, figure out how not to turn of ssl authentication +* Fix the circleci pipeline * live testing -* search * sandbox Missing api stuff * user authentication * user update * user verbose output +* all the user key stuff + +The search doc needs to be clearer. It refers to the knife search string but isn't clear that what it means diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search new file mode 100755 index 0000000..ebb991d --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search @@ -0,0 +1,9 @@ +#!/bin/bash + +# Group testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds +go run ${BASE}/../cmd/search/search.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/search/search.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/search/search.go new file mode 100644 index 0000000..1935107 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/search/search.go @@ -0,0 +1,108 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "time" + + "github.com/go-chef/chef" + "chefapi_test/testapi" +) + +func main() { + // Add nodes + client := testapi.Client() + addNodes(client) + // Give the nodes time to end up in all of the data bases. An immediate search will show no nodes + time.Sleep(10 * time.Second) + + // TODO: Search limit is hardcoded to 1000, figure out how to do paging and to set the limit + + // List Indexes + indexes, err := client.Search.Indexes() + if err != nil { + fmt.Fprintln(os.Stderr, "Issue listing indexes ", err) + } + fmt.Printf("List indexes %+v EndIndex\n", indexes) + + // build an invalid seach query + query, err := client.Search.NewQuery("node", "name") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue building invalid query", err) + } + + // build a seach query + query, err = client.Search.NewQuery("node", "name:node*") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue building query ", err) + } + fmt.Printf("List new query %+v\n", query) + + // Run the query + res, err := query.Do(client) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue running query ", err) + } + fmt.Printf("List nodes from query %+v\n", res) + + // You can also use the service to run a query + res, err = client.Search.Exec("node", "name:node1") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue running Search.Exec() ", err) + } + fmt.Printf("List nodes from Exec query %+v\n", res) + // dump out results back in json as an example + fmt.Println("JSON output example") + jsonData, err := json.MarshalIndent(res, "", "\t") + os.Stdout.Write(jsonData) + os.Stdout.WriteString("\n") + + res, err = client.Search.Exec("node", "name:*") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue running Search.Exec() ", err) + } + fmt.Printf("List nodes from all nodes Exec query %+v\n", res) + + // Partial search + part := make(map[string]interface{}) + part["name"] = []string{"name"} + pres, err := client.Search.PartialExec("node", "*:*", part) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue running Search.PartialExec()", err) + } + fmt.Printf("List nodes from partial search %+v\n", pres) + + // Clean up nodes + deleteNodes(client) +} + +func addNodes(client *chef.Client) { + for i := 0; i < 2; i++ { + node := chef.NewNode("node" + fmt.Sprintf("%d", i)) + _, err := client.Nodes.Post(node) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue adding node", node, err) + } + bode := chef.NewNode("bode" + fmt.Sprintf("%d", i)) + _, err = client.Nodes.Post(bode) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue adding node", node, err) + } + } + return +} + +func deleteNodes(client *chef.Client) { + for i := 0; i < 2; i++ { + err := client.Nodes.Delete("node" + fmt.Sprintf("%d", i)) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting node", err) + } + err = client.Nodes.Delete("bode" + fmt.Sprintf("%d", i)) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting node", err) + } + } + return +} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go index b731540..714b11a 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go @@ -5,7 +5,6 @@ package main import ( "fmt" - "github.com/go-chef/chef" "chefapi_test/testapi" "os" diff --git a/examples/chefapi_examples/test/integration/default/inspec/search_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/search_spec.rb new file mode 100644 index 0000000..e13f988 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/search_spec.rb @@ -0,0 +1,13 @@ +# Inspec tests for the search chef api go module +# + +describe command('/go/src/chefapi_test/bin/search') do + its('stderr') { should match(/^Issue building invalid query statement is malformed/) } + its('stderr') { should_not match(/node/) } + its('stdout') { should match(%r{^List indexes map\[(?=.*node:https://localhost/organizations/test/search/node)(?=.*role:https://localhost/organizations/test/search/role)(?=.*client:https://localhost/organizations/test/search/client)(?=.*environment:https://localhost/organizations/test/search/environment).*\] EndIndex}) } + its('stdout') { should match(/^List new query node\?q=name:node\*\&rows=1000\&sort=X_CHEF_id_CHEF_X asc\&start=0/) } + its('stdout') { should match(/^List nodes from query \{Total:2 Start:0 Rows:\[/) } + its('stdout') { should match(/^List nodes from Exec query \{Total:1 Start:0 Rows:\[/) } + its('stdout') { should match(/^List nodes from all nodes Exec query \{Total:4 Start:0 Rows:\[/) } + its('stdout') { should match(/^List nodes from partial search \{Total:4 Start:0 Rows:\[/) } +end diff --git a/search.go b/search.go index 3cb0a03..a7544ee 100644 --- a/search.go +++ b/search.go @@ -81,10 +81,10 @@ func (e SearchService) NewQuery(idx, statement string) (query SearchQuery, err e return } -// Exec runs the query on the index passed in. This is a helper method. If you want more controll over the query use NewQuery and its Do() method. -// BUG(spheromak): Should we use exec or SearchQuery.Do() or have both ? +// Exec runs the query on the index passed in. This is a helper method. If you want more control over the query use NewQuery and its Do() method. +// BUG(spheromak): Should we use Exec or SearchQuery.Do() or have both ? func (e SearchService) Exec(idx, statement string) (res SearchResult, err error) { - // Copy-paste here till We decide which way to go with exec vs Do + // Copy-paste here till We decide which way to go with Exec vs Do if !strings.Contains(statement, ":") { err = errors.New("statement is malformed") return From 6624c35266ffc8d8b3bec06eca33e59a007de717 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 30 Dec 2019 12:07:09 -0800 Subject: [PATCH 26/65] Debugging circleci --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index b7ebab7..3aec077 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,6 +40,9 @@ jobs: - 35:v45:41:ba:cf:3f:7f:d5:00:0f:11:6b:4d:c0:a1:90 - checkout # specify any bash command here prefixed with `run: ` + - run: pwd + - run: env + - run: ls -Rl - run: go get -v *.go - run: go get github.com/ctdk/goiardi/chefcrypto - run: go get github.com/smartystreets/goconvey/convey From 05fe91bd71dab10c5575daa1608ed255d2f327ba Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 30 Dec 2019 12:25:24 -0800 Subject: [PATCH 27/65] Add release.go to try to clean up git get issues --- cookbook_download.go | 2 +- cookbook_download_test.go | 74 +++++++++++++++++++-------------------- release.go | 5 +++ 3 files changed, 43 insertions(+), 38 deletions(-) create mode 100644 release.go diff --git a/cookbook_download.go b/cookbook_download.go index 91f8b33..4e8a9f1 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -65,7 +65,7 @@ func (c *CookbookService) DownloadTo(name, version, localDir string) error { // DownloadAt is a deprecated alias for DownloadTo func (c *CookbookService) DownloadAt(name, version, localDir string) error { err := c.DownloadTo(name, version, localDir) - return err + return err } // downloadCookbookItems downloads all the provided cookbook items into the provided diff --git a/cookbook_download_test.go b/cookbook_download_test.go index 9b6b1f2..bb7312e 100644 --- a/cookbook_download_test.go +++ b/cookbook_download_test.go @@ -195,43 +195,43 @@ func TestCookbooksDownloadAt(t *testing.T) { } ` - tempDir, err := ioutil.TempDir("", "foo-cookbook") - if err != nil { - t.Error(err) - } - defer os.RemoveAll(tempDir) // clean up - - mux.HandleFunc("/cookbooks/foo/0.2.1", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, string(mockedCookbookResponseFile)) - }) - mux.HandleFunc("/bookshelf/foo/metadata_rb", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "name 'foo'") - }) - mux.HandleFunc("/bookshelf/foo/default_rb", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "log 'this is a resource'") - }) - - err = client.Cookbooks.DownloadAt("foo", "0.2.1", tempDir) - assert.Nil(t, err) - - var ( - cookbookPath = path.Join(tempDir, "foo-0.2.1") - metadataPath = path.Join(cookbookPath, "metadata.rb") - recipesPath = path.Join(cookbookPath, "recipes") - defaultPath = path.Join(recipesPath, "default.rb") - ) - assert.DirExistsf(t, cookbookPath, "the cookbook directory should exist") - assert.DirExistsf(t, recipesPath, "the recipes directory should exist") - if assert.FileExistsf(t, metadataPath, "a metadata.rb file should exist") { - metadataBytes, err := ioutil.ReadFile(metadataPath) - assert.Nil(t, err) - assert.Equal(t, "name 'foo'", string(metadataBytes)) - } - if assert.FileExistsf(t, defaultPath, "the default.rb recipes should exist") { - recipeBytes, err := ioutil.ReadFile(defaultPath) - assert.Nil(t, err) - assert.Equal(t, "log 'this is a resource'", string(recipeBytes)) - } + tempDir, err := ioutil.TempDir("", "foo-cookbook") + if err != nil { + t.Error(err) + } + defer os.RemoveAll(tempDir) // clean up + + mux.HandleFunc("/cookbooks/foo/0.2.1", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, string(mockedCookbookResponseFile)) + }) + mux.HandleFunc("/bookshelf/foo/metadata_rb", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "name 'foo'") + }) + mux.HandleFunc("/bookshelf/foo/default_rb", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "log 'this is a resource'") + }) + + err = client.Cookbooks.DownloadAt("foo", "0.2.1", tempDir) + assert.Nil(t, err) + + var ( + cookbookPath = path.Join(tempDir, "foo-0.2.1") + metadataPath = path.Join(cookbookPath, "metadata.rb") + recipesPath = path.Join(cookbookPath, "recipes") + defaultPath = path.Join(recipesPath, "default.rb") + ) + assert.DirExistsf(t, cookbookPath, "the cookbook directory should exist") + assert.DirExistsf(t, recipesPath, "the recipes directory should exist") + if assert.FileExistsf(t, metadataPath, "a metadata.rb file should exist") { + metadataBytes, err := ioutil.ReadFile(metadataPath) + assert.Nil(t, err) + assert.Equal(t, "name 'foo'", string(metadataBytes)) + } + if assert.FileExistsf(t, defaultPath, "the default.rb recipes should exist") { + recipeBytes, err := ioutil.ReadFile(defaultPath) + assert.Nil(t, err) + assert.Equal(t, "log 'this is a resource'", string(recipeBytes)) + } } func TestVerifyMD5Checksum(t *testing.T) { diff --git a/release.go b/release.go new file mode 100644 index 0000000..34ac92d --- /dev/null +++ b/release.go @@ -0,0 +1,5 @@ +// +build !debug + +package chef + +func debug(fmt string, args ...interface{}) {} From e92d65ed50fc1b3ef2764fb1cef0d219ef7aaacd Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 30 Dec 2019 12:36:30 -0800 Subject: [PATCH 28/65] Fixing .circleci pipeline job --- .circleci/config.yml | 6 +++--- debug.go | 2 +- release.go | 5 ----- 3 files changed, 4 insertions(+), 9 deletions(-) delete mode 100644 release.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 3aec077..c676648 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,9 +43,9 @@ jobs: - run: pwd - run: env - run: ls -Rl - - run: go get -v *.go + - run: go get - run: go get github.com/ctdk/goiardi/chefcrypto - run: go get github.com/smartystreets/goconvey/convey - run: go get github.com/stretchr/testify/assert - - run: go vet *.go - - run: go test *.go + - run: go vet + - run: go test diff --git a/debug.go b/debug.go index d59297c..47943a2 100644 --- a/debug.go +++ b/debug.go @@ -1,4 +1,4 @@ -// +build debug +// +build !debug package chef diff --git a/release.go b/release.go deleted file mode 100644 index 34ac92d..0000000 --- a/release.go +++ /dev/null @@ -1,5 +0,0 @@ -// +build !debug - -package chef - -func debug(fmt string, args ...interface{}) {} From 6e51b37e73ec2fe44c72408ff37f20e6e41795dc Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 30 Dec 2019 16:00:49 -0800 Subject: [PATCH 29/65] Add the start of the sandbox item testing The sandbox.go code has a bad url. The sample code doesn't upload. Need to fix these issues outside of this PR --- examples/README.md | 14 +++- examples/chefapi_examples/TODO.md | 12 +++ .../default/go/src/chefapi_test/bin/sandbox | 9 +++ .../src/chefapi_test/cmd/sandbox/sandbox.go | 80 +++++++++++++++++++ examples/chefapi_examples/recipes/chefapi.rb | 8 ++ .../default/inspec/sandbox_spec.rb | 14 ++++ sandbox.go | 6 +- 7 files changed, 139 insertions(+), 4 deletions(-) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/sandbox/sandbox.go create mode 100644 examples/chefapi_examples/test/integration/default/inspec/sandbox_spec.rb diff --git a/examples/README.md b/examples/README.md index 6fa10e9..4b10588 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,7 +1,8 @@ # Examples for using the api + ## Coded samples The code directory has examples of using the client code to exercise the chef api end points. -The bin directory has command for invoking the code. +The bin directory has commands for invoking the code. The chefapi_examples cookbook creates a chef server instance. Then runs the code and verifies the resulting output using inspec tests. @@ -9,3 +10,14 @@ the resulting output using inspec tests. Run kitchen converge to create a chef server instance in a linux image running under vagrant. Run kitchen verify to code run the go examples that using the client api and to see confirm that the results are as expected. + +## Looking at the output from an api call +Sometimes you might want to see what output gets created by an api call. Inspec tends to hide +and mask the output. You can use kitchen login to access the linux image. Use "cd /go/src/chefapi_test/bin" +to access the bin directory and run any of the commands to run the api sample use code and see +the results. + +## Creating a client +On the test image /go/src/chefapi_test/testapi/testapi.go has code that creates a client +for use by other api calls. For the purposes of testing using the pivotal user and key +for all the tests works but seems like a really bad idea for any production use. diff --git a/examples/chefapi_examples/TODO.md b/examples/chefapi_examples/TODO.md index e4194d9..d897b75 100644 --- a/examples/chefapi_examples/TODO.md +++ b/examples/chefapi_examples/TODO.md @@ -12,3 +12,15 @@ Missing api stuff * all the user key stuff The search doc needs to be clearer. It refers to the knife search string but isn't clear that what it means + +Sandbox upload gets a 403. https://stackoverflow.com/questions/28807834/chef-upload-to-sandbox-fails-with-403-unauthorized +It's not using the client, going to a returned url and give an auth code. Not sure what to do with it. +embedded/lib/ruby/gems/2.6.0/gems/chef-15.6.10/lib/chef/cookbook_uploader.rb has the ruby code to do this stuff + +2879448ee0bb891f2103f21891234b2f:{Url:https://localhost:443/bookshelf/organization-87e84b155d98af0a68fb7e69e7adee0a/checksum-2879448ee0bb891f2103f21891234b2f?AWSAccessKeyId=3d54e25d43d14490faa55cf85826a2732e3c9411&Expires=1577447683&Signature=cQYAocsq%2B15ztGX3zEcmJ1C9HKk%3D Upload:true} 2b26f685cf7bce1b634787df20607aeb:{Url:https://localhost:443/bookshelf/organization-87e84b155d98af0a68fb7e69e7adee0a/checksum-2b26f685cf7bce1b634787df20607aeb?AWSAccessKeyId=3d54e25d43d14490faa55cf85826a2732e3c9411&Expires=1577447683&Signature=HBsn1DnQWuItPVxQNtCG7Rjc18w%3D Upload:true} 73008070bb42cbbe05dfa5427a8416b1:{Url:https://localhost:443/bookshelf/organization-87e84b155d98af0a68fb7e69e7adee0a/checksum-73008070bb42cbbe05dfa5427a8416b1?AWSAccessKeyId=3d54e25d43d14490faa55cf85826a2732e3c9411&Expires=1577447683&Signature=5RrhZNMvUU%2B4D5fZkYsuYdClWdQ%3D Upload:true}]} + +Uploading: 2879448ee0bb891f2103f21891234b2f ---> {https://localhost:443/bookshelf/organization-87e84b155d98af0a68fb7e69e7adee0a/checksum-2879448ee0bb891f2103f21891234b2f?AWSAccessKeyId=3d54e25d43d14490faa55cf85826a2732e3c9411&Expires=1577447683&Signature=cQYAocsq%2B15ztGX3zEcmJ1C9HKk%3D true} + +after request &{PUT https://localhost:443/bookshelf/organization-87e84b155d98af0a68fb7e69e7adee0a/checksum-2879448ee0bb891f2103f21891234b2f?AWSAccessKeyId=3d54e25d43d14490faa55cf85826a2732e3c9411&Expires=1577447683&Signature=cQYAocsq%2B15ztGX3zEcmJ1C9HKk%3D HTTP/1.1 1 1 map[X-Ops-Authorization-6:[bDFCEcSiDHAqCqwtvwNP8rX/0HgDwO4cCvaNJQT7uw==] Content-Type:[application/octet-stream] Method:[PUT] X-Ops-Userid:[pivotal] X-Ops-Authorization-2:[qMNYPwz2Ym6awXiCgOy6YXxGZeCvfvTngGPvdDa/FPVJLt5MUdYBNOBk/L1W] X-Ops-Authorization-5:[OG9NI2aOPOnah512Cav2l7H/mgdM4gr3+jyuz/sGQVxAv5jriu086zKqU/EJ] X-Ops-Content-Hash:[RWKT0i3TZ5n3l3km2zgS6JlPs9M=] X-Ops-Timestamp:[2019-12-27T11:39:43Z] X-Ops-Authorization-4:[PY95MHZVNgaH3gcYj+dD3GHCtDlk44C4vHEHv3N5OVTfrcs7Mi3tuprPARuh] Accept:[application/json] X-Ops-Authorization-1:[wFXZeEpvMefVCPDVGUZL/zNMlqx94yN5L78rq13TJticQNXCXJg45Y74fwKY] X-Ops-Authorization-3:[7V1BquFUsAJxsKkhiGsvT0l6qUTNFIKOGc2KOH6p0cGpPlzaDBoEyd8Qer0U] X-Chef-Version:[11.12.0] X-Ops-Sign:[algorithm=sha1;version=1.0]] {0xc42006ddd0} 0x604110 128 [] false localhost:443 map[] map[] map[] } +before upload +after upload PUT https://localhost:443/bookshelf/organization-87e84b155d98af0a68fb7e69e7adee0a/checksum-2879448ee0bb891f2103f21891234b2f?AWSAccessKeyId=3d54e25d43d14490faa55cf85826a2732e3c9411&Expires=1577447683&Signature=cQYAocsq%2B15ztGX3zEcmJ1C9HKk%3D: 403 diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox new file mode 100755 index 0000000..8d791ad --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox @@ -0,0 +1,9 @@ +#!/bin/bash + +# Group testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds +go run ${BASE}/../cmd/sandbox/sandbox.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/sandbox/sandbox.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/sandbox/sandbox.go new file mode 100644 index 0000000..44deac7 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/sandbox/sandbox.go @@ -0,0 +1,80 @@ +package main + +import ( + "bytes" + "crypto/md5" + "crypto/rand" + "fmt" + "os" + + "github.com/cenkalti/backoff" + "chefapi_test/testapi" +) + +//random_data makes random byte slice for building junk sandbox data +func random_data(size int) (b []byte) { + b = make([]byte, size) + rand.Read(b) + return +} + +func main() { + client := testapi.Client() + + // create junk files and sums + files := make(map[string][]byte) + sums := make([]string, 10) + for i := 0; i < 10; i++ { + data := random_data(128) + hashstr := fmt.Sprintf("%x", md5.Sum(data)) + files[hashstr] = data + sums[i] = hashstr + } + + // TODO: Find a sandbox delete method + + // post the new sums and get a new sandbox id + postResp, err := client.Sandboxes.Post(sums) + if err != nil { + fmt.Fprintf(os.Stderr, "Issue making request: %+v\n", err) + } + fmt.Printf("Create sandboxes %+v\n", postResp) + + // Let's upload the files that postRep thinks we should upload + for hash, item := range postResp.Checksums { + if item.Upload == true { + if hash == "" { + continue + } + // If you were writing this in your own tool you could just use the FH and let the Reader interface suck out the content instead of doing the convert. + fmt.Printf("\nUploading: %s ---> %v\n\n", hash, item) + req, err := client.NewRequest("PUT", item.Url, bytes.NewReader(files[hash])) + // TODO: headers = { "content-type" => "application/x-binary", "content-md5" => checksum64, "accept" => "application/json" } + if err != nil { + fmt.Println(os.Stderr, "Issue this shouldn't happen:", err) + } + + // post the files + upload := func() error { + _, err = client.Do(req, nil) + return err + } + + // with exp backoff + err = upload + fmt.Println(os.Stderr, "Issue posting files to the sandbox: ", err) + // TODO: backoff of 4xx and 5xx doesn't make sense + // err = backoff.Retry(upload, backoff.NewExponentialBackOff()) + // if err != nil { + // fmt.Println(os.Stderr, "Issue posting files to the sandbox: ", err) + // } + } + } + + // Now lets tell the server we have uploaded all the things. + sandbox, err := client.Sandboxes.Put(postResp.ID) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue commiting sandbox: ", err) + } + fmt.Printf("Resulting sandbox %+v\n", sandbox) +} diff --git a/examples/chefapi_examples/recipes/chefapi.rb b/examples/chefapi_examples/recipes/chefapi.rb index 558e8d6..e54fe0c 100644 --- a/examples/chefapi_examples/recipes/chefapi.rb +++ b/examples/chefapi_examples/recipes/chefapi.rb @@ -9,11 +9,19 @@ recursive true end +directory '/go/src/github.com/cenkalti' do + recursive true +end + # TODO: allow for testing a branch or the version of the api this cookbook is embedded in git '/go/src/github.com/go-chef/chef' do repository 'https://github.com/go-chef/chef.git' end +git '/go/src/github.com/cenkalti/backoff' do + repository 'https://github.com/cenkalti/backoff' +end + remote_directory 'local_go' do files_backup false path '/go' diff --git a/examples/chefapi_examples/test/integration/default/inspec/sandbox_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/sandbox_spec.rb new file mode 100644 index 0000000..6d86c9f --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/sandbox_spec.rb @@ -0,0 +1,14 @@ +# Inspec tests for the sandbox chef api go module +# + +describe command('/go/src/chefapi_test/bin/sandbox') do + # TODO: Get the sandbox sample code to work - upload files is failing + + # its('stderr') { should_not match(%r{Issue}) } + # its('stderr') { should_not match(%r{error}) } + # its('stderr') { should_not match(%r{no such file}) } + # its('stderr') { should_not match(%r{cannot find}) } + # its('stderr') { should_not match(%r{not used|undefined}) } + # its('stdout') { should match(/^Create sandboxes /) } + # its('stdout') { should match(/^Resulting sandboxes /) } +end diff --git a/sandbox.go b/sandbox.go index 43d48ec..cc7cc11 100644 --- a/sandbox.go +++ b/sandbox.go @@ -22,13 +22,13 @@ type SandboxPostResponse struct { Checksums map[string]SandboxItem } -// A SandbooxItem is embeddedinto the response from the chef-server and the actual sandbox It is the Url and state for a specific Item. +// A SandboxItem is embedded into the response from the chef-server and the actual sandbox is the Url and state for a specific Item. type SandboxItem struct { Url string `json:"url"` Upload bool `json:"needs_upload"` } -// Sandbox Is the structure of an actul sandbox that has been created and returned by the final PUT to the sandbox ID +// Sandbox Is the structure of an actual sandbox that has been created and returned by the final PUT to the sandbox ID type Sandbox struct { ID string `json:"guid"` Name string `json:"name"` @@ -55,7 +55,7 @@ func (s SandboxService) Post(sums []string) (data SandboxPostResponse, err error return } -// Put is used to commit a sandbox ID to the chef server. To singal that the sandox you have Posted is now uploaded. +// Put is used to commit a sandbox ID to the chef server. To signal that the sandbox you have Posted is now uploaded. func (s SandboxService) Put(id string) (box Sandbox, err error) { answer := make(map[string]bool) answer["is_completed"] = true From edb592b92f70e6bfe4ce3a1a094517d67f05c51e Mon Sep 17 00:00:00 2001 From: Ben Burry Date: Thu, 11 Oct 2018 21:01:28 +0000 Subject: [PATCH 30/65] Add missing pagination logic in PartialExec The library has a bug in the PartialExec function where it will only return the first page of search results. To address this, I've implemented similar logic to the Exec function to traverse all pages in the result set and accumulate their rows before returning. There is enough of a similarity that it may be an indicator for a need to eventually combine the logic of the two functions. --- search.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/search.go b/search.go index a7544ee..bafb624 100644 --- a/search.go +++ b/search.go @@ -136,6 +136,27 @@ func (e SearchService) PartialExec(idx, statement string, params map[string]inte } err = e.client.magicRequestDecoder("POST", fullUrl, body, &res) + if err != nil { + return + } + + // the total rows available for this query across all pages + total := res.Total + // the maximum number of rows in each page + inc := query.Rows + paged_res := SearchResult{} + + for start := res.Start; start+inc <= total; start += inc { + query.Start = start + inc + fullUrl = fmt.Sprintf("search/%s", query) + + err = e.client.magicRequestDecoder("POST", fullUrl, body, &paged_res) + if err != nil { + return + } + // accumulate this page of results into the primary SearchResult instance + res.Rows = append(res.Rows, paged_res.Rows...) + } return } From 474172805eb865d4e08fa53689ee223c48338d80 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 30 Dec 2019 20:16:05 -0800 Subject: [PATCH 31/65] Merge PR #102 to fix pagination in partial searches Fix the +build debug code, it was broken trying to fix the .circleci run --- debug.go | 2 +- .../go/src/chefapi_test/bin/search_pagination | 9 +++ .../cmd/search/search_pagination.go | 58 +++++++++++++++++++ .../default/inspec/cookbook_spec.rb | 1 + .../integration/default/inspec/group_spec.rb | 1 + .../integration/default/inspec/node_spec.rb | 1 + .../default/inspec/organization_spec.rb | 3 +- .../default/inspec/sandbox_spec.rb | 5 +- .../default/inspec/search_paginate_spec.rb | 8 +++ .../integration/default/inspec/search_spec.rb | 1 + .../integration/default/inspec/user_spec.rb | 1 + release.go | 6 ++ 12 files changed, 90 insertions(+), 6 deletions(-) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/search/search_pagination.go create mode 100644 examples/chefapi_examples/test/integration/default/inspec/search_paginate_spec.rb create mode 100644 release.go diff --git a/debug.go b/debug.go index 47943a2..d59297c 100644 --- a/debug.go +++ b/debug.go @@ -1,4 +1,4 @@ -// +build !debug +// +build debug package chef diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination new file mode 100755 index 0000000..682d899 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination @@ -0,0 +1,9 @@ +#!/bin/bash + +# Group testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds +go run ${BASE}/../cmd/search/search_pagination.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/search/search_pagination.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/search/search_pagination.go new file mode 100644 index 0000000..22cb516 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/search/search_pagination.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/go-chef/chef" + "chefapi_test/testapi" +) + +func main() { + // Add nodes + client := testapi.Client() + addNodes(client) + // Give the nodes time to end up in the search data bases. An immediate search will show no nodes + time.Sleep(10 * time.Second) + + // Stanard search + res, err := client.Search.Exec("node", "name:node*") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue running Search.Exec() ", err) + } + fmt.Printf("List nodes from Exec query Total:%+v\n", res.Total) + + // Partial search + part := make(map[string]interface{}) + part["name"] = []string{"name"} + pres, err := client.Search.PartialExec("node", "*:*", part) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue running Search.PartialExec()", err) + } + fmt.Printf("List nodes from partial search Total:%+v\n", pres.Total) + + // Clean up nodes + deleteNodes(client) +} + +func addNodes(client *chef.Client) { + for i := 0; i < 1200; i++ { + node := chef.NewNode("node" + fmt.Sprintf("%d", i)) + _, err := client.Nodes.Post(node) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue adding node", node, err) + } + } + return +} + +func deleteNodes(client *chef.Client) { + for i := 0; i < 1200; i++ { + err := client.Nodes.Delete("node" + fmt.Sprintf("%d", i)) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting node", err) + } + } + return +} diff --git a/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb index 7828f09..243ea2a 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/cookbook_spec.rb @@ -2,6 +2,7 @@ # describe command('/go/src/chefapi_test/bin/cookbook') do its('stderr') { should match(%r{^Issue getting cookbook nothere: GET https://localhost/organizations/test/cookbooks/nothere: 404}) } + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } its('stderr') { should_not match(/testbook/) } its('stderr') { should_not match(/sampbook/) } its('stderr') { should_not match(/Issue loading/) } diff --git a/examples/chefapi_examples/test/integration/default/inspec/group_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/group_spec.rb index 6817ec1..35f7b7a 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/group_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/group_spec.rb @@ -3,6 +3,7 @@ describe command('/go/src/chefapi_test/bin/group') do its('stderr') { should match(%r{^Issue recreating group1. POST https://localhost/organizations/test/groups: 409}) } + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } its('stdout') { should match(%r{^List initial groups map\[(?=.*admins:https://localhost/organizations/test/groups/admins)(?=.*billing-admins:https://localhost/organizations/test/groups/billing-admins)(?=.*clients:https://localhost/organizations/test/groups/clients)(?=.*users:https://localhost/organizations/test/groups/users)(?=.*public_key_read_access:https://localhost/organizations/test/groups/public_key_read_access).*\]EndInitialList}) } its('stdout') { should match(%r{^Added group1 \&\{https://localhost/organizations/test/groups/group1\}}) } its('stdout') { should match(%r{^List groups after adding group1 map\[(?=.*group1:https://localhost/organizations/test/groups/group1)(?=.*admins:https://localhost/organizations/test/groups/admins)(?=.*billing-admins:https://localhost/organizations/test/groups/billing-admins)(?=.*clients:https://localhost/organizations/test/groups/clients)(?=.*users:https://localhost/organizations/test/groups/users)(?=.*public_key_read_access:https://localhost/organizations/test/groups/public_key_read_access).*\]EndAddList}) } diff --git a/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb index d868a7c..43583e1 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb @@ -3,6 +3,7 @@ describe command('/go/src/chefapi_test/bin/node') do its('stderr') { should match(%r{^Couldn't recreate node node1. POST https://localhost/organizations/test/nodes: 409}) } + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } its('stdout') { should match(/^List initial nodes map\[\]$/) } its('stdout') { should match(/^Define node1 {node1 _default.*Chef::Node \[pwn\]/) } its('stdout') { should match(%r{^Added node1 \&\{https://localhost/organizations/test/nodes/node1\}}) } diff --git a/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb index fa662ab..e8c7fa2 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/organization_spec.rb @@ -2,10 +2,11 @@ # describe command('/go/src/chefapi_test/bin/organization') do + its('stderr') { should match(%r{^Issue creating org: {org1 organization1 } POST https://localhost/organizations: 409$}) } + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } its('stdout') { should match(/^List initial organizations map\[test.*test\]$/) } its('stdout') { should match(/^Added org1 {org1-validator -----BEGIN RSA PRIVATE KEY-----/) } its('stdout') { should match(/^Added org1 again { }$/) } - its('stderr') { should match(%r{^Issue creating org: {org1 organization1 } POST https://localhost/organizations: 409$}) } its('stdout') { should match(/^Added org2 {org2-validator -----BEGIN RSA PRIVATE KEY-----.*$/) } its('stdout') { should match(/^Get org1 {org1 organization1 [0-9a-f]+}$/) } its('stdout') { should match(%r{^List organizations After adding org1 and org2 map(?=.*org2:https://localhost/organizations/org2)(?=.*test:https://localhost/organizations/test)(?=.*org1:https://localhost/organizations/org1)}) } diff --git a/examples/chefapi_examples/test/integration/default/inspec/sandbox_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/sandbox_spec.rb index 6d86c9f..39877ad 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/sandbox_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/sandbox_spec.rb @@ -5,10 +5,7 @@ # TODO: Get the sandbox sample code to work - upload files is failing # its('stderr') { should_not match(%r{Issue}) } - # its('stderr') { should_not match(%r{error}) } - # its('stderr') { should_not match(%r{no such file}) } - # its('stderr') { should_not match(%r{cannot find}) } - # its('stderr') { should_not match(%r{not used|undefined}) } + # its('stderr') { should_not match(%r{error|no such file|cannot find|not used|undefined}) } # its('stdout') { should match(/^Create sandboxes /) } # its('stdout') { should match(/^Resulting sandboxes /) } end diff --git a/examples/chefapi_examples/test/integration/default/inspec/search_paginate_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/search_paginate_spec.rb new file mode 100644 index 0000000..344fdc5 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/search_paginate_spec.rb @@ -0,0 +1,8 @@ +# Inspec tests for the search chef api go module +# + +describe command('/go/src/chefapi_test/bin/search_pagination') do + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } + its('stdout') { should match(/^List nodes from Exec query Total:1200/) } + its('stdout') { should match(/^List nodes from partial search Total:1200/) } +end diff --git a/examples/chefapi_examples/test/integration/default/inspec/search_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/search_spec.rb index e13f988..0b8450b 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/search_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/search_spec.rb @@ -4,6 +4,7 @@ describe command('/go/src/chefapi_test/bin/search') do its('stderr') { should match(/^Issue building invalid query statement is malformed/) } its('stderr') { should_not match(/node/) } + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } its('stdout') { should match(%r{^List indexes map\[(?=.*node:https://localhost/organizations/test/search/node)(?=.*role:https://localhost/organizations/test/search/role)(?=.*client:https://localhost/organizations/test/search/client)(?=.*environment:https://localhost/organizations/test/search/environment).*\] EndIndex}) } its('stdout') { should match(/^List new query node\?q=name:node\*\&rows=1000\&sort=X_CHEF_id_CHEF_X asc\&start=0/) } its('stdout') { should match(/^List nodes from query \{Total:2 Start:0 Rows:\[/) } diff --git a/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb index 3442131..3153c08 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb @@ -3,6 +3,7 @@ describe command('/go/src/chefapi_test/bin/user') do its('stderr') { should match(%r{^Issue creating user: POST https://localhost/users: 409}) } + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } its('stdout') { should match(%r{^List initial users map\[(?=.*pivotal:https://localhost/users/pivotal).*\] EndInitialList}) } its('stdout') { should match(%r{^Add usr1 \{https://localhost/users/usr1 -----BEGIN RSA}) } its('stdout') { should match(%r{^Filter users map\[usr1:https://localhost/users/usr1\]}) } diff --git a/release.go b/release.go new file mode 100644 index 0000000..feaec67 --- /dev/null +++ b/release.go @@ -0,0 +1,6 @@ +// +build !debug + +package chef + +func debug(fmt string, args ...interface{}) { +} From 29a98e95bef5f333cfab3216a7dffa867c6fdda8 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 30 Dec 2019 21:37:09 -0800 Subject: [PATCH 32/65] The sandbox endpoint is relative to an organization /sandboxes needs to be sandboxes. --- sandbox.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sandbox.go b/sandbox.go index cc7cc11..6726ce2 100644 --- a/sandbox.go +++ b/sandbox.go @@ -15,7 +15,7 @@ type SandboxRequest struct { Checksums map[string]interface{} `json:"checksums"` } -// SandboxPostResponse is the struct returned from the chef-server for Post Requests to /sandbox +// SandboxPostResponse is the struct returned from the chef-server for Post Requests to /sandboxes type SandboxPostResponse struct { ID string `json:"sandbox_id"` Uri string `json:"uri"` @@ -51,7 +51,7 @@ func (s SandboxService) Post(sums []string) (data SandboxPostResponse, err error return } - err = s.client.magicRequestDecoder("POST", "/sandboxes", body, &data) + err = s.client.magicRequestDecoder("POST", "sandboxes", body, &data) return } @@ -65,6 +65,6 @@ func (s SandboxService) Put(id string) (box Sandbox, err error) { return box, fmt.Errorf("must supply sandbox id to PUT request.") } - err = s.client.magicRequestDecoder("PUT", "/sandboxes/"+id, body, &box) + err = s.client.magicRequestDecoder("PUT", "sandboxes/"+id, body, &box) return } From 681ec99d604eacff78726a6d22cf5c6dc1ec216a Mon Sep 17 00:00:00 2001 From: markgibbons Date: Tue, 31 Dec 2019 12:25:13 -0800 Subject: [PATCH 33/65] Add comments to the run_list_item functions --- run_list_item.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/run_list_item.go b/run_list_item.go index 757ce61..3ed26e4 100644 --- a/run_list_item.go +++ b/run_list_item.go @@ -19,7 +19,8 @@ var ( falseFriendRegexp *regexp.Regexp = regexp.MustCompile(falseFriend) ) -// This is a direct port of the Chef::RunList::RunListItem class +// RunListItem external representation of a run list +// This module is a direct port of the Chef::RunList::RunListItem class // see: https://github.com/chef/chef/blob/master/lib/chef/run_list/run_list_item.rb type RunListItem struct { Name string @@ -27,6 +28,7 @@ type RunListItem struct { Version string } +// NewRunListItem parses a single item from a run list and returns a structure func NewRunListItem(item string) (rli RunListItem, err error) { switch { case qualifiedRecipeRegexp.MatchString(item): @@ -69,6 +71,7 @@ func NewRunListItem(item string) (rli RunListItem, err error) { return rli, nil } +// String implements the String interface function func (r RunListItem) String() (s string) { if r.Version != "" { s = fmt.Sprintf("%s[%s@%s]", r.Type, r.Name, r.Version) @@ -79,10 +82,12 @@ func (r RunListItem) String() (s string) { return s } +// IsRecipe Determines if the runlist item is a recipe func (r RunListItem) IsRecipe() bool { return r.Type == "recipe" } +// IsRole Determines if the runlist item is a role func (r RunListItem) IsRole() bool { return r.Type == "role" } From 2e7bbfaa21aa8fd3c4f5486d339157807908a9b5 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Wed, 1 Jan 2020 17:50:03 -0800 Subject: [PATCH 34/65] Add support for the authenticate_user endpoint --- authenticate.go | 20 ++++++ authenticate_test.go | 62 +++++++++++++++++++ cookbook_test.go | 1 - .../go/src/chefapi_test/bin/authenticate | 10 +++ .../cmd/authenticate/authenticate.go | 51 +++++++++++++++ .../cmd/organization/organization.go | 28 --------- .../go/src/chefapi_test/cmd/user/user.go | 4 +- .../default/inspec/authenticate.rb | 7 +++ examples/inspec | 1 + http.go | 32 +++++----- test/authenticate.json | 4 ++ user.go | 1 + 12 files changed, 175 insertions(+), 46 deletions(-) create mode 100644 authenticate.go create mode 100644 authenticate_test.go create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/authenticate/authenticate.go create mode 100644 examples/chefapi_examples/test/integration/default/inspec/authenticate.rb create mode 120000 examples/inspec create mode 100644 test/authenticate.json diff --git a/authenticate.go b/authenticate.go new file mode 100644 index 0000000..48da0f9 --- /dev/null +++ b/authenticate.go @@ -0,0 +1,20 @@ +package chef + +type AuthenticateUserService struct { + client *Client +} + +// Authenticate represents the body of the /authenticate_user request +type Authenticate struct { + UserName string `json:"username"` + Password string `json:"password"` +} + +// Authenticate performs an authentication attempt. +// +// https://docs.chef.io/api_chef_server.html#authenticate-user +func (e *AuthenticateUserService) Authenticate(authenticate_request Authenticate) (err error) { + body, err := JSONReader(authenticate_request) + err = e.client.magicRequestDecoder("POST", "authenticate_user", body, nil) + return +} diff --git a/authenticate_test.go b/authenticate_test.go new file mode 100644 index 0000000..34142e4 --- /dev/null +++ b/authenticate_test.go @@ -0,0 +1,62 @@ +package chef + +import ( + "encoding/json" + "io" + "log" + "net/http" + "os" + "testing" + "github.com/stretchr/testify/assert" +) + +var ( + testAuthenticateJSON = "test/authenticate.json" +) + +func TestAuthenticateFromJSONDecoder(t *testing.T) { + if file, err := os.Open(testAuthenticateJSON); err != nil { + t.Error("Unexpected error '", err, "' during os.Open on", testAuthenticateJSON) + } else { + dec := json.NewDecoder(file) + var g Authenticate + if err := dec.Decode(&g); err == io.EOF { + log.Fatal(g) + } else if err != nil { + log.Fatal(err) + } + } +} + +func TestAuthenticatesCreate(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/authenticate_user", func(w http.ResponseWriter, r *http.Request) { + dec := json.NewDecoder(r.Body) + var request Authenticate + dec.Decode(&request) + switch { + case r.Method == "POST": + if request.Password == "password" { + w.WriteHeader(200) + } else { + w.WriteHeader(401) + } + } + }) + var request Authenticate + request.UserName = "user1" + request.Password = "invalid" + err := client.AuthenticateUser.Authenticate(request) + if assert.NotNil(t, err) { + assert.Contains(t, err.Error(), "401") + } + + request.UserName = "user1" + request.Password = "password" + err = client.AuthenticateUser.Authenticate(request) + if err != nil { + t.Errorf("Authenticate returned error: %+v", err) + } +} diff --git a/cookbook_test.go b/cookbook_test.go index b70999f..c667549 100644 --- a/cookbook_test.go +++ b/cookbook_test.go @@ -4,7 +4,6 @@ import ( "fmt" "io/ioutil" "net/http" - //"os" "testing" "github.com/stretchr/testify/assert" diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate new file mode 100755 index 0000000..9830cfa --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate @@ -0,0 +1,10 @@ +#!/bin/bash + +# Authenticate_user testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds + +go run ${BASE}/../cmd/authenticate/authenticate.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/authenticate/authenticate.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/authenticate/authenticate.go new file mode 100644 index 0000000..27d7a39 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/authenticate/authenticate.go @@ -0,0 +1,51 @@ +// +// Test the go-chef/chef chef authenticate_user api endpoint against a live server +// +package main + +import ( + "fmt" + "os" + + "github.com/go-chef/chef" + "chefapi_test/testapi" +) + + +// main Exercise the chef server api +func main() { + // Create a client for access + client := testapi.Client() + + // Create a user + var usr1 chef.User + usr1 = chef.User{ UserName: "usr1", + Email: "user1@domain.io", + FirstName: "user1", + LastName: "fullname", + DisplayName: "User1 Fullname", + Password: "Logn12ComplexPwd#", + } + userResult := createUser(client, usr1) + + var ar Authenticate + // Authenticate with a valid password + ar.UserName = "usr1" + ar.Password = "Logn12ComplexPwd#" + err := client.AuthenticateUser.Authenticate(ar) + fmt.Printf("Authenticate with a valid password %+v", err) + + // Authenticate with an invalid password + ar.Password = "Logn12ComplexPwd#" + err = client.AuthenticateUser.Authenticate(ar) + fmt.Printf("Authenticate with an invalid password %+v", err) +} + +// createUser uses the chef server api to create a single user +func createUser(client *chef.Client, user chef.User) chef.UserResult { + usrResult, err := client.Users.Create(user) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue creating user:", err) + } + return usrResult +} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go index e298fc3..450b3b0 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go @@ -61,33 +61,6 @@ func main() { } -// buildClient creates a connection to a chef server using the chef api. -func buildClient(user string, keyfile string, baseurl string, skipssl bool) *chef.Client { - key := clientKey(keyfile) - client, err := chef.NewClient(&chef.Config{ - Name: user, - Key: string(key), - BaseURL: baseurl, - SkipSSL: skipssl, - // goiardi is on port 4545 by default, chef-zero is 8889, chef-server is on 443 - }) - if err != nil { - fmt.Fprintln(os.Stderr, "Issue setting up client:", err) - os.Exit(1) - } - return client -} - -// clientKey reads the pem file containing the credentials needed to use the chef client. -func clientKey(filepath string) string { - key, err := ioutil.ReadFile(filepath) - if err != nil { - fmt.Fprintln(os.Stderr, "Couldn't read key.pem:", err) - os.Exit(1) - } - return string(key) -} - // createOrganization uses the chef server api to create a single organization func createOrganization(client *chef.Client, org chef.Organization) chef.OrganizationResult { orgResult, err := client.Organizations.Create(org) @@ -108,7 +81,6 @@ func deleteOrganization(client *chef.Client, name string) error { // getOrganization uses the chef server api to get information for a single organization func getOrganization(client *chef.Client, name string) chef.Organization { - // todo: everything orgList, err := client.Organizations.Get(name) if err != nil { fmt.Fprintln(os.Stderr, "Issue listing org:", name, err) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go index 714b11a..9f5c60c 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go @@ -66,7 +66,7 @@ func main() { fmt.Printf("List after cleanup %+v EndCleanupList\n", userList) } -// createUser uses the chef server api to create a single organization +// createUser uses the chef server api to create a single user func createUser(client *chef.Client, user chef.User) chef.UserResult { usrResult, err := client.Users.Create(user) if err != nil { @@ -75,7 +75,7 @@ func createUser(client *chef.Client, user chef.User) chef.UserResult { return usrResult } -// deleteUser uses the chef server api to delete a single organization +// deleteUser uses the chef server api to delete a single user func deleteUser(client *chef.Client, name string) (err error) { err = client.Users.Delete(name) if err != nil { diff --git a/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb b/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb new file mode 100644 index 0000000..c697ea2 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb @@ -0,0 +1,7 @@ +# Inspec tests for the authenticate_user chef api go module +# + +describe command('/go/src/chefapi_test/bin/authenticate') do + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } + its('stdout') { should match(/^List initial organizations map\[test.*test\]$/) } +end diff --git a/examples/inspec b/examples/inspec new file mode 120000 index 0000000..b141d77 --- /dev/null +++ b/examples/inspec @@ -0,0 +1 @@ +chefapi_examples/test/integration/default/inspec \ No newline at end of file diff --git a/http.go b/http.go index 971be32..77a66d5 100644 --- a/http.go +++ b/http.go @@ -40,22 +40,23 @@ type Client struct { BaseURL *url.URL client *http.Client - ACLs *ACLService - Clients *ApiClientService - Cookbooks *CookbookService - DataBags *DataBagService - Environments *EnvironmentService - Groups *GroupService - Nodes *NodeService - Organizations *OrganizationService - Principals *PrincipalService - Roles *RoleService - Sandboxes *SandboxService - Search *SearchService - Users *UserService + ACLs *ACLService + AuthenticateUser *AuthenticateUserService + Clients *ApiClientService + Cookbooks *CookbookService + DataBags *DataBagService + Environments *EnvironmentService + Groups *GroupService + Nodes *NodeService + Organizations *OrganizationService + Principals *PrincipalService + Roles *RoleService + Sandboxes *SandboxService + Search *SearchService + Users *UserService } -// Config contains the configuration options for a chef client. This is Used primarily in the NewClient() constructor in order to setup a proper client object +// Config contains the configuration options for a chef client. This structure is used primarily in the NewClient() constructor in order to setup a proper client object type Config struct { // This should be the user ID on the chef server Name string @@ -63,7 +64,7 @@ type Config struct { // This is the plain text private Key for the user Key string - // BaseURL is the chef server URL used to connect to. If using orgs you should include your org in the url + // BaseURL is the chef server URL used to connect to. If using orgs you should include your org in the url and terminate the url with a "/" BaseURL string // When set to false (default) this will enable SSL Cert Verification. If you need to disable Cert Verification set to true @@ -154,6 +155,7 @@ func NewClient(cfg *Config) (*Client, error) { BaseURL: baseUrl, } c.ACLs = &ACLService{client: c} + c.AuthenticateUser = &AuthenticateUserService{client: c} c.Clients = &ApiClientService{client: c} c.Cookbooks = &CookbookService{client: c} c.DataBags = &DataBagService{client: c} diff --git a/test/authenticate.json b/test/authenticate.json new file mode 100644 index 0000000..a1b91d4 --- /dev/null +++ b/test/authenticate.json @@ -0,0 +1,4 @@ +{ + "username": "grantmc", + "password": "p@ssw0rd" +} diff --git a/user.go b/user.go index 710efa3..f24acbd 100644 --- a/user.go +++ b/user.go @@ -102,6 +102,7 @@ func (e *UserService) Get(name string) (user User, err error) { // TODO: // API /users/USERNAME GET external_authentication_uid and email filters +// note that the combination of verbose and filters is not supported // API /users/USERNAME GET verbose parameter // API /users/USERNAME PUT // API /users/USERNAME/keys GET From ca2aa88d71f69f68051853e2fa4472233929377e Mon Sep 17 00:00:00 2001 From: markgibbons Date: Wed, 1 Jan 2020 21:50:51 -0800 Subject: [PATCH 35/65] Add the authenticate tests #minor Update spelling and comments --- .../cmd/authenticate/authenticate.go | 33 ++++++++++++------- .../src/chefapi_test/cmd/cookbook/cookbook.go | 1 - .../cmd/organization/organization.go | 1 - examples/chefapi_examples/recipes/chefapi.rb | 2 +- .../default/inspec/authenticate.rb | 3 +- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/authenticate/authenticate.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/authenticate/authenticate.go index 27d7a39..760efec 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/authenticate/authenticate.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/authenticate/authenticate.go @@ -18,27 +18,30 @@ func main() { client := testapi.Client() // Create a user - var usr1 chef.User - usr1 = chef.User{ UserName: "usr1", - Email: "user1@domain.io", - FirstName: "user1", + var usr chef.User + usr = chef.User{ UserName: "usrauth", + Email: "usrauth@domain.io", + FirstName: "usrauth", LastName: "fullname", - DisplayName: "User1 Fullname", + DisplayName: "Userauth Fullname", Password: "Logn12ComplexPwd#", } - userResult := createUser(client, usr1) + createUser(client, usr) - var ar Authenticate + var ar chef.Authenticate // Authenticate with a valid password - ar.UserName = "usr1" + ar.UserName = "usrauth" ar.Password = "Logn12ComplexPwd#" err := client.AuthenticateUser.Authenticate(ar) - fmt.Printf("Authenticate with a valid password %+v", err) + fmt.Printf("Authenticate with a valid password %+vauthenticate\n", err) // Authenticate with an invalid password - ar.Password = "Logn12ComplexPwd#" + ar.Password = "badpassword" err = client.AuthenticateUser.Authenticate(ar) - fmt.Printf("Authenticate with an invalid password %+v", err) + fmt.Printf("Authenticate with an invalid password %+v\n", err) + + // Cleanup + deleteUser(client, "usrauth") } // createUser uses the chef server api to create a single user @@ -49,3 +52,11 @@ func createUser(client *chef.Client, user chef.User) chef.UserResult { } return usrResult } +// deleteUser uses the chef server api to delete a single user +func deleteUser(client *chef.Client, name string) (err error) { + err = client.Users.Delete(name) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting org:", err) + } + return +} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go index c128cbd..81773fe 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go @@ -8,7 +8,6 @@ import ( "os" "os/exec" - // chef "github.com/go-chef/chef" "chefapi_test/testapi" ) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go index 450b3b0..b1eb780 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/organization/organization.go @@ -5,7 +5,6 @@ package main import ( "fmt" - "io/ioutil" "os" "github.com/go-chef/chef" diff --git a/examples/chefapi_examples/recipes/chefapi.rb b/examples/chefapi_examples/recipes/chefapi.rb index e54fe0c..ae3a0e0 100644 --- a/examples/chefapi_examples/recipes/chefapi.rb +++ b/examples/chefapi_examples/recipes/chefapi.rb @@ -13,7 +13,7 @@ recursive true end -# TODO: allow for testing a branch or the version of the api this cookbook is embedded in +puts "BRANCH #{node['chefapi_examples']['go_chef_branch']}" git '/go/src/github.com/go-chef/chef' do repository 'https://github.com/go-chef/chef.git' end diff --git a/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb b/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb index c697ea2..1bfb498 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb @@ -3,5 +3,6 @@ describe command('/go/src/chefapi_test/bin/authenticate') do its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } - its('stdout') { should match(/^List initial organizations map\[test.*test\]$/) } + its('stdout') { should match(/^Authenticate with a valid password \/) } + its('stdout') { should match(/^Authenticate with an invalid password POST https:\/\/localhost\/authenticate_user: 401/) } end From 318ac7c4069b8a2b955def31ebc6c926f2cf65a2 Mon Sep 17 00:00:00 2001 From: "Marc A. Paradise" Date: Tue, 12 Nov 2019 11:51:32 -0500 Subject: [PATCH 36/65] Do not download cookbook files that are already download Signed-off-by: Marc A. Paradise --- cookbook_download.go | 6 +++ cookbook_download_test.go | 107 +++++++++++++++++++++++++++++++++++--- 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/cookbook_download.go b/cookbook_download.go index 4e8a9f1..909a98e 100644 --- a/cookbook_download.go +++ b/cookbook_download.go @@ -93,6 +93,12 @@ func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType, func (c *CookbookService) downloadCookbookFile(item CookbookItem, localPath string) error { filePath := path.Join(localPath, item.Name) + // First check and see if the file is already there - if it is and the checksum + // matches, there's no need to redownload it. + if verifyMD5Checksum(filePath, item.Checksum) { + return nil + } + request, err := c.client.NewRequest("GET", item.Url, nil) if err != nil { return err diff --git a/cookbook_download_test.go b/cookbook_download_test.go index bb7312e..20c373f 100644 --- a/cookbook_download_test.go +++ b/cookbook_download_test.go @@ -72,11 +72,8 @@ func TestCookbooksDownloadEmptyWithVersion(t *testing.T) { assert.Nil(t, err) } -func TestCookbooksDownloadTo(t *testing.T) { - setup() - defer teardown() - - mockedCookbookResponseFile := ` +func cookbookData() string { + return ` { "version": "0.2.1", "name": "foo-0.2.1", @@ -111,9 +108,59 @@ func TestCookbooksDownloadTo(t *testing.T) { "templates": [], "metadata": {}, "access": {} +} ` } -` +func TestCookbooksDownloadTo(t *testing.T) { + setup() + defer teardown() + + mockedCookbookResponseFile := cookbookData() + tempDir, err := ioutil.TempDir("", "foo-cookbook") + if err != nil { + t.Error(err) + } + defer os.RemoveAll(tempDir) // clean up + + mux.HandleFunc("/cookbooks/foo/0.2.1", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, string(mockedCookbookResponseFile)) + }) + mux.HandleFunc("/bookshelf/foo/metadata_rb", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "name 'foo'") + }) + mux.HandleFunc("/bookshelf/foo/default_rb", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "log 'this is a resource'") + }) + + err = client.Cookbooks.DownloadTo("foo", "0.2.1", tempDir) + assert.Nil(t, err) + + var ( + cookbookPath = path.Join(tempDir, "foo-0.2.1") + metadataPath = path.Join(cookbookPath, "metadata.rb") + recipesPath = path.Join(cookbookPath, "recipes") + defaultPath = path.Join(recipesPath, "default.rb") + ) + assert.DirExistsf(t, cookbookPath, "the cookbook directory should exist") + assert.DirExistsf(t, recipesPath, "the recipes directory should exist") + if assert.FileExistsf(t, metadataPath, "a metadata.rb file should exist") { + metadataBytes, err := ioutil.ReadFile(metadataPath) + assert.Nil(t, err) + assert.Equal(t, "name 'foo'", string(metadataBytes)) + } + if assert.FileExistsf(t, defaultPath, "the default.rb recipes should exist") { + recipeBytes, err := ioutil.ReadFile(defaultPath) + assert.Nil(t, err) + assert.Equal(t, "log 'this is a resource'", string(recipeBytes)) + } + +} + +func TestCookbooksDownloadTo_caching(t *testing.T) { + setup() + defer teardown() + + mockedCookbookResponseFile := cookbookData() tempDir, err := ioutil.TempDir("", "foo-cookbook") if err != nil { t.Error(err) @@ -151,6 +198,54 @@ func TestCookbooksDownloadTo(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "log 'this is a resource'", string(recipeBytes)) } + + // Capture the timestamps to ensure that on-redownload of unchanged cookook, + // they show no modification (using this as a proxy to determine whether + // the file has been re-downloaded). + defaultPathInfo, err := os.Stat(defaultPath) + assert.Nil(t, err) + + metaDataInfo, err := os.Stat(metadataPath) + assert.Nil(t, err) + + err = client.Cookbooks.DownloadTo("foo", "0.2.1", tempDir) + assert.Nil(t, err) + + defaultPathNewInfo, err := os.Stat(defaultPath) + assert.Nil(t, err) + + metaDataNewInfo, err := os.Stat(metadataPath) + assert.Nil(t, err) + + err = client.Cookbooks.DownloadTo("foo", "0.2.1", tempDir) + assert.Nil(t, err) + + // If the file was not re-downloaded, we would expect the timestamp + // to remain unchanged. + assert.Equal(t, defaultPathInfo.ModTime(), defaultPathNewInfo.ModTime()) + assert.Equal(t, metaDataInfo.ModTime(), metaDataNewInfo.ModTime()) + + err = os.Truncate(metadataPath, 1) + assert.Nil(t, err) + + err = os.Chtimes(metadataPath, metaDataInfo.ModTime(), metaDataInfo.ModTime()) + assert.Nil(t, err) + + err = client.Cookbooks.DownloadTo("foo", "0.2.1", tempDir) + assert.Nil(t, err) + + metaDataNewInfo, err = os.Stat(metadataPath) + assert.Nil(t, err) + + assert.NotEqual(t, metaDataInfo.ModTime(), metaDataNewInfo.ModTime()) + + // Finally, make sure the modified-and-replaced metadata.rb is matching + // what we expect after we have redownloaded the cookbook: + if assert.FileExistsf(t, metadataPath, "a metadata.rb file should exist") { + metadataBytes, err := ioutil.ReadFile(metadataPath) + assert.Nil(t, err) + assert.Equal(t, "name 'foo'", string(metadataBytes)) + } } func TestCookbooksDownloadAt(t *testing.T) { From bf7c58dc7710be3da1b73cc3448f098f8c131287 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Wed, 1 Jan 2020 23:07:24 -0800 Subject: [PATCH 37/65] Fix the code merge --- cookbook_download_test.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/cookbook_download_test.go b/cookbook_download_test.go index 0c4de81..cd7c593 100644 --- a/cookbook_download_test.go +++ b/cookbook_download_test.go @@ -266,21 +266,3 @@ func TestVerifyMD5Checksum(t *testing.T) { assert.Nil(t, err) assert.True(t, verifyMD5Checksum(filePath, "70bda176ac4db06f1f66f96ae0693be1")) } - -func TestVerifyMD5Checksum(t *testing.T) { - tempDir, err := ioutil.TempDir("", "md5-test") - if err != nil { - t.Error(err) - } - defer os.RemoveAll(tempDir) // clean up - - var ( - // if someone changes the test data, - // you have to also update the below md5 sum - testData = []byte("hello\nchef\n") - filePath = path.Join(tempDir, "dat") - ) - err = ioutil.WriteFile(filePath, testData, 0644) - assert.Nil(t, err) - assert.True(t, verifyMD5Checksum(filePath, "70bda176ac4db06f1f66f96ae0693be1")) -} From b65a52c90b79e1a5c94f19689702625add89cd7e Mon Sep 17 00:00:00 2001 From: markgibbons Date: Thu, 2 Jan 2020 14:26:34 -0800 Subject: [PATCH 38/65] Add get invitation id by user name and accept invitation Separate the structs for invitation requests in in the list output Add debug code to show the http response to help diagnose json reader failures Add a structure for delete invitation response Use an array of invites instead InviteList structure Add inspec tests for association --- association.go | 141 ++++++++++++++++++ .../go/src/chefapi_test/bin/association | 11 ++ .../cmd/association/association.go | 77 ++++++++++ .../cmd/association/association_cleanup.go | 30 ++++ .../cmd/association/association_setup.go | 48 ++++++ .../default/inspec/association_spec.rb | 13 ++ http.go | 41 ++--- 7 files changed, 344 insertions(+), 17 deletions(-) create mode 100644 association.go create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association.go create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_cleanup.go create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_setup.go create mode 100644 examples/chefapi_examples/test/integration/default/inspec/association_spec.rb diff --git a/association.go b/association.go new file mode 100644 index 0000000..0e3b832 --- /dev/null +++ b/association.go @@ -0,0 +1,141 @@ +package chef + +// import "fmt" +import "errors" + +type AssociationService struct { + client *Client +} + +// Association represents the native Go version of the deserialized Association type +// Chef API docs: https://docs.chef.io/api_chef_server.html#association-requests +// https://github.com/chef/chef-server/blob/master/src/oc_erchef/apps/oc_chef_wm/src/oc_chef_wm_org_invites.erl Invitation implementation +// https://github.com/chef/chef-server/blob/master/src/oc_erchef/apps/oc_chef_wm/src/oc_chef_wm_org_associations.erl user org associations +type Association struct { + Uri string `json:"uri"` // the last part of the uri is the invitation id + OrganizationUser struct { + UserName string `json:"username,omitempty"` + } `json:"organization_user"` + Organization struct { + Name string `json:"name,omitempty"` + } `json:"organization"` + User struct { + Email string `json:"email,omitempty"` + FirstName string `json:"first_name,omitempty"` + } `json:"user"` +} + +type RescindInvite struct { + Id string `json:"id,omitempty"` + Orgname string `json:"orgname,omitempty"` + Username string `json:"username,omitempty"` +} + +// type InviteList struct { + // Invites []Invite +// } + +type Invite struct { + Id string `json:"id,omitempty"` + UserName string `json:"username,omitempty"` +} + +type Request struct { + User string `json:"user"` +} + +// Need return info for all of these requests + +// GET /organizations/:orgname/association_requests association::ListInvites +// POST /organizations/:orgname/association_requests association::Invite need body format +// DELETE /organizations/:orgname/association_requests/:id association:DeleteInvite + +// PUT chef_rest.put "users/#{username}/association_requests/#{association_id}", { response: "accept" } AcceptInvite +// rest.get_rest("association_requests").each { |i| @invites[i['username']] = i['id'] } find the id based on the name + +// /organization/:orgname/users no doc at all for this +// Get - list association::List +// Post - Add user immediately association::add need body format +// /organization/:orgname/users/:username +// Get - user details association::get +// Delete - remove user association::delete + +// Get gets a list of the pending invitations for an organization. +func (e *AssociationService) ListInvites() (invitelist []Invite, err error) { + err = e.client.magicRequestDecoder("GET", "association_requests", nil, &invitelist) + return +} + +// Creates an invitation for a user to join an organization on the chef server +func (e *AssociationService) Invite(invite Request) (data Association, err error) { + body, err := JSONReader(invite) + if err != nil { + return + } + err = e.client.magicRequestDecoder("POST", "association_requests/", body, &data) + return +} + +// Delete removes a pending invitation to an organization +func (e *AssociationService) DeleteInvite(id string) (rescind RescindInvite, err error) { + err = e.client.magicRequestDecoder("DELETE", "association_requests/"+id, nil, &rescind) + return +} + +// InviteID Finds an invitation id for a user +func (e *AssociationService) InviteId(user string) (id string, err error) { + var invitelist []Invite + err = e.client.magicRequestDecoder("GET", "association_requests", nil, &invitelist) + if err != nil { + return + } + // Find an invite for the user or return err + for _, in := range invitelist { + if in.UserName == user { + id = in.Id + } + } + if id == "" { + err = errors.New("User request not found") + } + return +} + +// AcceptInvite Accepts an invitation +// TODO: Gets a 405, code is in knife is it part of erchef? +func (e *AssociationService) AcceptInvite(id string) (data string, err error) { + body, err := JSONReader("{ \"accept\" }") + if err != nil { + return + } + err = e.client.magicRequestDecoder("PUT", "association_requests/"+id, body, &data) + return +} + +// Get a list of the users in an organization +func (e *AssociationService) List() (data string, err error) { + err = e.client.magicRequestDecoder("GET", "users", nil, &data) + return +} + +// Add a user immediately +func (e *AssociationService) Add(invite Invite) (data string, err error) { + body, err := JSONReader(invite) + if err != nil { + return + } + err = e.client.magicRequestDecoder("POST", "users", body, &data) + return +} + +// Get the details of a user in an organization +func (e *AssociationService) Get(name string) (data string, err error) { + err = e.client.magicRequestDecoder("GET", "users/"+name, nil, &data) + return +} + +// Delete removes a user from an organization +func (e *AssociationService) Delete(name string) (data map[string]string, err error) { + err = e.client.magicRequestDecoder("DELETE", "users/"+name, nil, &data) + return +} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association new file mode 100755 index 0000000..2c3388c --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association @@ -0,0 +1,11 @@ +#!/bin/bash + +# Group testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds +go run ${BASE}/../cmd/association/association_setup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true +go run ${BASE}/../cmd/association/association.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true +go run ${BASE}/../cmd/association/association_cleanup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association.go new file mode 100644 index 0000000..283503b --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association.go @@ -0,0 +1,77 @@ +// +// Test the go-chef/chef chef server api /organization/:org/user and /organization/:org/association_requests +// endpoints against a live server +// +package main + +import ( + "fmt" + "chefapi_test/testapi" + "github.com/go-chef/chef" + "os" +) + + +// main Exercise the chef server api +func main() { + client := testapi.Client() + + // Build stuctures to invite users + invite := chef.Request { + User: "usrinvite", + } + invite2 := chef.Request { + User: "usr2invite", + } + invitemissing := chef.Request { + User: "nouser", + } + + // Invite the user to the test org + out, err := client.Associations.Invite(invite) + if err != nil { + fmt.Fprintf(os.Stderr, "Issue inviting a user %+v %+v\n", invite, err) + } + fmt.Printf("Invited user %+v %+v\n", invite, out) + + // Invite a second user + out, err = client.Associations.Invite(invite2) + fmt.Printf("Invited user %+v %+v\n", invite2, out) + + // fail at inviting a missing user. Should get a 404 + out, err = client.Associations.Invite(invitemissing) + if err != nil { + fmt.Fprintf(os.Stderr, "Issue inviting a user %+v %+v\n", invitemissing, err) + } + fmt.Printf("Invited user %+v %+v\n", invitemissing, out) + + // Find the pending invitation by user name + id, err := client.Associations.InviteId("usr2invite") + if err != nil { + fmt.Fprintf(os.Stderr, "Issue finding an invitation for usr2invite %+v\n", err) + } + fmt.Printf("Invitation id for usr2invite %+v\n", id) + + // Accept the invite for invite2 + // outa, err := client.Associations.AcceptInvite(id) + // if err != nil { + // fmt.Fprintf(os.Stderr, "Issue accepting the invitation %+v\n", err) + // } + // fmt.Printf("Accept invitation %+v\n", outa) + + // List the invites + outl, err := client.Associations.ListInvites() + if err != nil { + fmt.Fprintf(os.Stderr, "Issue listing the invitations %+v\n", err) + } + fmt.Printf("Invitation list %+v\n", outl) + + // Delete the invitations by id + for _, in := range outl { + outd, err := client.Associations.DeleteInvite(in.Id) + if err != nil { + fmt.Fprintf(os.Stderr, "Issue deleting an invitation for %s %+v\n", in.UserName, err) + } + fmt.Printf("Deleted invitation %s for %s %+v\n", in.Id, in.UserName, outd) + } +} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_cleanup.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_cleanup.go new file mode 100644 index 0000000..ad524a4 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_cleanup.go @@ -0,0 +1,30 @@ +// +// Test the go-chef/chef chef server api /organization/:org/user and /organization/:org/association_requests +// endpoints against a live server +// +package main + +import ( + "fmt" + "chefapi_test/testapi" + "github.com/go-chef/chef" + "os" +) + + +// main Exercise the chef server api +func main() { + client := testapi.Client() + deleteUser(client, "usrinvite") + deleteUser(client, "usr2invite") + +} + + // deleteUser uses the chef server api to delete a single user + func deleteUser(client *chef.Client, name string) (err error) { + err = client.Users.Delete(name) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting org:", err) + } + return + } diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_setup.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_setup.go new file mode 100644 index 0000000..ec67df0 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_setup.go @@ -0,0 +1,48 @@ +// +// Test the go-chef/chef chef server api /organization/:org/user and /organization/:org/association_requests +// endpoints against a live server +// +package main + +import ( + "fmt" + "chefapi_test/testapi" + "github.com/go-chef/chef" + "os" +) + + +// main Exercise the chef server api +func main() { + client := testapi.Client() + + // Create a user + var usr chef.User + usr = chef.User{ UserName: "usrinvite", + Email: "usrauth@domain.io", + FirstName: "usr", + LastName: "invite", + DisplayName: "Userauth Fullname", + Password: "Logn12ComplexPwd#", + } + createUser(client, usr) + + usr = chef.User{ UserName: "usr2invite", + Email: "usr22auth@domain.io", + FirstName: "usr22", + LastName: "invite", + DisplayName: "User22auth Fullname", + Password: "Logn12ComplexPwd#", + } + createUser(client, usr) + +} + +// createUser uses the chef server api to create a single user +func createUser(client *chef.Client, user chef.User) chef.UserResult { + usrResult, err := client.Users.Create(user) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue creating user:", err) + } + return usrResult +} diff --git a/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb new file mode 100644 index 0000000..de1bdb8 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb @@ -0,0 +1,13 @@ +# Inspec tests for the associations chef api go module +# +describe command('/go/src/chefapi_test/bin/association') do + its('stderr') { should match(%r{^Issue inviting a user \{User:nouser\} .* 404}) } + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } + its('stderr') { should_not match(/testbook/) } + its('stderr') { should_not match(/sampbook/) } + its('stdout') { should match(%r{^Invited user \{User:usrinvite\} \{Uri:https://localhost/organizations/test/association_requests/[a-f0-9]+ OrganizationUser:\{UserName:pivotal\} Organization:\{Name:test\} User:\{Email:usrauth@domain.io FirstName:usr\}\}}) } + its('stdout') { should match(%r{^Invited user \{User:usr2invite\} \{Uri:https://localhost/organizations/test/association_requests/[a-f0-9]+ OrganizationUser:\{UserName:pivotal\} Organization:\{Name:test\} User:\{Email:usr22auth@domain.io FirstName:usr22\}\}}) } + its('stdout') { should match(%r{^Invitation id for usr2invite [a-f0-9]+}) } + its('stdout') { should match(%r{^Invitation list \[(?=.*\{Id:[a-f0-9]+ UserName:usr2invite\})(?=.*\{Id:[a-f0-9]+ UserName:usrinvite\})}) } + its('stdout') { should match(%r{^Deleted invitation [a-f0-9]+ for usrinvite \{Id:[a-f0-9]+ Orgname:test Username:usrinvite\}}) } +end diff --git a/http.go b/http.go index 77a66d5..c85547d 100644 --- a/http.go +++ b/http.go @@ -40,20 +40,21 @@ type Client struct { BaseURL *url.URL client *http.Client - ACLs *ACLService + ACLs *ACLService + Associations *AssociationService AuthenticateUser *AuthenticateUserService - Clients *ApiClientService - Cookbooks *CookbookService - DataBags *DataBagService - Environments *EnvironmentService - Groups *GroupService - Nodes *NodeService - Organizations *OrganizationService - Principals *PrincipalService - Roles *RoleService - Sandboxes *SandboxService - Search *SearchService - Users *UserService + Clients *ApiClientService + Cookbooks *CookbookService + DataBags *DataBagService + Environments *EnvironmentService + Groups *GroupService + Nodes *NodeService + Organizations *OrganizationService + Principals *PrincipalService + Roles *RoleService + Sandboxes *SandboxService + Search *SearchService + Users *UserService } // Config contains the configuration options for a chef client. This structure is used primarily in the NewClient() constructor in order to setup a proper client object @@ -156,6 +157,7 @@ func NewClient(cfg *Config) (*Client, error) { } c.ACLs = &ACLService{client: c} c.AuthenticateUser = &AuthenticateUserService{client: c} + c.Associations = &AssociationService{client: c} c.Clients = &ApiClientService{client: c} c.Cookbooks = &CookbookService{client: c} c.DataBags = &DataBagService{client: c} @@ -183,7 +185,7 @@ func (c *Client) magicRequestDecoder(method, path string, body io.Reader, v inte if res != nil { defer res.Body.Close() } - debug("Response: %+v \n", res) + debug("Response: %+v\n", res) if err != nil { return err } @@ -245,17 +247,21 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { } // BUG(fujin) tightly coupled - err = CheckResponse(res) // <-- + err = CheckResponse(res) if err != nil { return res, err } + var resbuf bytes.Buffer + restee := io.TeeReader(res.Body, &resbuf) if v != nil { if w, ok := v.(io.Writer); ok { - io.Copy(w, res.Body) + io.Copy(w, restee) } else { - err = json.NewDecoder(res.Body).Decode(v) + err = json.NewDecoder(restee).Decode(v) if err != nil { + resbody, _ := ioutil.ReadAll(&resbuf) + debug("Response body: %+v\n", string(resbody)) return res, err } } @@ -263,6 +269,7 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { return res, nil } + // SignRequest modifies headers of an http.Request func (ac AuthConfig) SignRequest(request *http.Request) error { // sanitize the path for the chef-server From 2fd2df557072a02fba7cdc48e5d548267861c85b Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 5 Jan 2020 21:03:37 -0800 Subject: [PATCH 39/65] Add initial organization user testing Use a new structure for adding users immediately Add structures for the output from the org user methods Add inspec tests for the org user functions Formatting and update doc --- association.go | 70 +++++++++---------- examples/README.md | 5 +- .../cmd/association/association.go | 31 +++++++- .../cmd/association/association_cleanup.go | 1 + .../cmd/association/association_setup.go | 9 +++ .../default/inspec/association_spec.rb | 12 ++-- http.go | 5 +- 7 files changed, 87 insertions(+), 46 deletions(-) diff --git a/association.go b/association.go index 0e3b832..cab5128 100644 --- a/association.go +++ b/association.go @@ -7,36 +7,46 @@ type AssociationService struct { client *Client } -// Association represents the native Go version of the deserialized Association type // Chef API docs: https://docs.chef.io/api_chef_server.html#association-requests // https://github.com/chef/chef-server/blob/master/src/oc_erchef/apps/oc_chef_wm/src/oc_chef_wm_org_invites.erl Invitation implementation // https://github.com/chef/chef-server/blob/master/src/oc_erchef/apps/oc_chef_wm/src/oc_chef_wm_org_associations.erl user org associations type Association struct { - Uri string `json:"uri"` // the last part of the uri is the invitation id - OrganizationUser struct { + Uri string `json:"uri"` // the last part of the uri is the invitation id + OrganizationUser struct { UserName string `json:"username,omitempty"` } `json:"organization_user"` - Organization struct { - Name string `json:"name,omitempty"` + Organization struct { + Name string `json:"name,omitempty"` } `json:"organization"` User struct { - Email string `json:"email,omitempty"` + Email string `json:"email,omitempty"` FirstName string `json:"first_name,omitempty"` - } `json:"user"` + } `json:"user"` } -type RescindInvite struct { - Id string `json:"id,omitempty"` - Orgname string `json:"orgname,omitempty"` - Username string `json:"username,omitempty"` +type OrgUserListEntry struct { + User struct { + Username string `json:"username,omitempty"` + } `json:"user,omitempty"` +} + +type OrgUser struct { + Username string `json:"username,omitempty"` + Email string `json:"email,omitempty"` + DisplayName string `json:"display_name,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + PublicKey string `json:"public_key,omitempty"` } -// type InviteList struct { - // Invites []Invite -// } +type RescindInvite struct { + Id string `json:"id,omitempty"` + Orgname string `json:"orgname,omitempty"` + Username string `json:"username,omitempty"` +} type Invite struct { - Id string `json:"id,omitempty"` + Id string `json:"id,omitempty"` UserName string `json:"username,omitempty"` } @@ -44,21 +54,9 @@ type Request struct { User string `json:"user"` } -// Need return info for all of these requests - -// GET /organizations/:orgname/association_requests association::ListInvites -// POST /organizations/:orgname/association_requests association::Invite need body format -// DELETE /organizations/:orgname/association_requests/:id association:DeleteInvite - -// PUT chef_rest.put "users/#{username}/association_requests/#{association_id}", { response: "accept" } AcceptInvite -// rest.get_rest("association_requests").each { |i| @invites[i['username']] = i['id'] } find the id based on the name - -// /organization/:orgname/users no doc at all for this -// Get - list association::List -// Post - Add user immediately association::add need body format -// /organization/:orgname/users/:username -// Get - user details association::get -// Delete - remove user association::delete +type AddNow struct { + Username string `json:"username"` +} // Get gets a list of the pending invitations for an organization. func (e *AssociationService) ListInvites() (invitelist []Invite, err error) { @@ -113,29 +111,29 @@ func (e *AssociationService) AcceptInvite(id string) (data string, err error) { } // Get a list of the users in an organization -func (e *AssociationService) List() (data string, err error) { +func (e *AssociationService) List() (data []OrgUserListEntry, err error) { err = e.client.magicRequestDecoder("GET", "users", nil, &data) return } // Add a user immediately -func (e *AssociationService) Add(invite Invite) (data string, err error) { - body, err := JSONReader(invite) +func (e *AssociationService) Add(addme AddNow) (err error) { + body, err := JSONReader(addme) if err != nil { return } - err = e.client.magicRequestDecoder("POST", "users", body, &data) + err = e.client.magicRequestDecoder("POST", "users", body, nil) return } // Get the details of a user in an organization -func (e *AssociationService) Get(name string) (data string, err error) { +func (e *AssociationService) Get(name string) (data OrgUser, err error) { err = e.client.magicRequestDecoder("GET", "users/"+name, nil, &data) return } // Delete removes a user from an organization -func (e *AssociationService) Delete(name string) (data map[string]string, err error) { +func (e *AssociationService) Delete(name string) (data OrgUser, err error) { err = e.client.magicRequestDecoder("DELETE", "users/"+name, nil, &data) return } diff --git a/examples/README.md b/examples/README.md index 4b10588..e28bd89 100644 --- a/examples/README.md +++ b/examples/README.md @@ -3,6 +3,7 @@ ## Coded samples The code directory has examples of using the client code to exercise the chef api end points. The bin directory has commands for invoking the code. +The inspec directory has inspec tests to verify the output from invoking the code. The chefapi_examples cookbook creates a chef server instance. Then runs the code and verifies the resulting output using inspec tests. @@ -15,9 +16,9 @@ that the results are as expected. Sometimes you might want to see what output gets created by an api call. Inspec tends to hide and mask the output. You can use kitchen login to access the linux image. Use "cd /go/src/chefapi_test/bin" to access the bin directory and run any of the commands to run the api sample use code and see -the results. +the results. Running the bin commands by adding the --tags debug option will show more detail. ## Creating a client On the test image /go/src/chefapi_test/testapi/testapi.go has code that creates a client -for use by other api calls. For the purposes of testing using the pivotal user and key +for use by other api calls. For the purposes of testing, using the pivotal user and key for all the tests works but seems like a really bad idea for any production use. diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association.go index 283503b..8ab694d 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association.go @@ -26,6 +26,9 @@ func main() { invitemissing := chef.Request { User: "nouser", } + add1 := chef.AddNow { + Username: "usradd", + } // Invite the user to the test org out, err := client.Associations.Invite(invite) @@ -45,7 +48,7 @@ func main() { } fmt.Printf("Invited user %+v %+v\n", invitemissing, out) - // Find the pending invitation by user name + // Find a pending invitation by user name id, err := client.Associations.InviteId("usr2invite") if err != nil { fmt.Fprintf(os.Stderr, "Issue finding an invitation for usr2invite %+v\n", err) @@ -60,6 +63,7 @@ func main() { // fmt.Printf("Accept invitation %+v\n", outa) // List the invites + outl, err := client.Associations.ListInvites() if err != nil { fmt.Fprintf(os.Stderr, "Issue listing the invitations %+v\n", err) @@ -74,4 +78,29 @@ func main() { } fmt.Printf("Deleted invitation %s for %s %+v\n", in.Id, in.UserName, outd) } + + // Add a user to the test organization + err = client.Associations.Add(add1) + if err != nil { + fmt.Fprintf(os.Stderr, "Issue adding user usradd: %+v\n", err) + } + fmt.Printf("User added: %+v\n", add1) + // List the users + ulist, err := client.Associations.List() + if err != nil { + fmt.Fprintf(os.Stderr, "Issue listing the users: %+v\n", err) + } + fmt.Printf("Users list: %+v\n", ulist) + // Get the user details + uget, err := client.Associations.Get("usradd") + if err != nil { + fmt.Fprintf(os.Stderr, "Issue getting user details: %+v\n", err) + } + fmt.Printf("User details: %+v\n", uget) + // Delete a user for the organization + udel, err := client.Associations.Get("usradd") + if err != nil { + fmt.Fprintf(os.Stderr, "Issue deleting usradd: %+v\n", err) + } + fmt.Printf("User deleted: %+v\n", udel) } diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_cleanup.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_cleanup.go index ad524a4..c4011af 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_cleanup.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_cleanup.go @@ -17,6 +17,7 @@ func main() { client := testapi.Client() deleteUser(client, "usrinvite") deleteUser(client, "usr2invite") + deleteUser(client, "usradd") } diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_setup.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_setup.go index ec67df0..62d4caa 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_setup.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/association/association_setup.go @@ -36,6 +36,15 @@ func main() { } createUser(client, usr) + usr = chef.User{ UserName: "usradd", + Email: "usradd@domain.io", + FirstName: "usr", + LastName: "add", + DisplayName: "UserAdd Fullname", + Password: "Logn12ComplexPwd#", + } + createUser(client, usr) + } // createUser uses the chef server api to create a single user diff --git a/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb index de1bdb8..6e4af99 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb @@ -1,13 +1,17 @@ # Inspec tests for the associations chef api go module # describe command('/go/src/chefapi_test/bin/association') do - its('stderr') { should match(%r{^Issue inviting a user \{User:nouser\} .* 404}) } + its('stderr') { should match(/^Issue inviting a user \{User:nouser\} .* 404/) } its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } its('stderr') { should_not match(/testbook/) } its('stderr') { should_not match(/sampbook/) } its('stdout') { should match(%r{^Invited user \{User:usrinvite\} \{Uri:https://localhost/organizations/test/association_requests/[a-f0-9]+ OrganizationUser:\{UserName:pivotal\} Organization:\{Name:test\} User:\{Email:usrauth@domain.io FirstName:usr\}\}}) } its('stdout') { should match(%r{^Invited user \{User:usr2invite\} \{Uri:https://localhost/organizations/test/association_requests/[a-f0-9]+ OrganizationUser:\{UserName:pivotal\} Organization:\{Name:test\} User:\{Email:usr22auth@domain.io FirstName:usr22\}\}}) } - its('stdout') { should match(%r{^Invitation id for usr2invite [a-f0-9]+}) } - its('stdout') { should match(%r{^Invitation list \[(?=.*\{Id:[a-f0-9]+ UserName:usr2invite\})(?=.*\{Id:[a-f0-9]+ UserName:usrinvite\})}) } - its('stdout') { should match(%r{^Deleted invitation [a-f0-9]+ for usrinvite \{Id:[a-f0-9]+ Orgname:test Username:usrinvite\}}) } + its('stdout') { should match(/^Invitation id for usr2invite [a-f0-9]+/) } + its('stdout') { should match(/^Invitation list \[(?=.*\{Id:[a-f0-9]+ UserName:usr2invite\})(?=.*\{Id:[a-f0-9]+ UserName:usrinvite\})/) } + its('stdout') { should match(/^Deleted invitation [a-f0-9]+ for usrinvite \{Id:[a-f0-9]+ Orgname:test Username:usrinvite\}/) } + its('stdout') { should match(/^User added: \{Username:usradd\}/) } + its('stdout') { should match(/^Users list: \[\{User:\{Username:usradd\}\}\]/) } + its('stdout') { should match(/^User details: \{Username:usradd Email:usradd@domain.io DisplayName:UserAdd Fullname FirstName:usr LastName:add PublicKey:-----BEGIN PUBLIC KEY-----/) } + its('stdout') { should match(/^User deleted: \{Username:usradd Email:usradd@domain.io DisplayName:UserAdd Fullname FirstName:usr LastName:add PublicKey:-----BEGIN PUBLIC KEY-----/) } end diff --git a/http.go b/http.go index c85547d..668be5e 100644 --- a/http.go +++ b/http.go @@ -253,7 +253,7 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { } var resbuf bytes.Buffer - restee := io.TeeReader(res.Body, &resbuf) + restee := io.TeeReader(res.Body, &resbuf) if v != nil { if w, ok := v.(io.Writer); ok { io.Copy(w, restee) @@ -261,7 +261,7 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { err = json.NewDecoder(restee).Decode(v) if err != nil { resbody, _ := ioutil.ReadAll(&resbuf) - debug("Response body: %+v\n", string(resbody)) + debug("Response body: %+v\n", string(resbody)) return res, err } } @@ -269,7 +269,6 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { return res, nil } - // SignRequest modifies headers of an http.Request func (ac AuthConfig) SignRequest(request *http.Request) error { // sanitize the path for the chef-server From f51dfaa5ada143c976264991a4322dbb63dc3cc6 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Wed, 8 Jan 2020 15:13:05 -0800 Subject: [PATCH 40/65] Add the org users endpoints #minor --- association.go | 54 ++++++++----- association_test.go | 184 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 19 deletions(-) create mode 100644 association_test.go diff --git a/association.go b/association.go index cab5128..996b255 100644 --- a/association.go +++ b/association.go @@ -10,6 +10,9 @@ type AssociationService struct { // Chef API docs: https://docs.chef.io/api_chef_server.html#association-requests // https://github.com/chef/chef-server/blob/master/src/oc_erchef/apps/oc_chef_wm/src/oc_chef_wm_org_invites.erl Invitation implementation // https://github.com/chef/chef-server/blob/master/src/oc_erchef/apps/oc_chef_wm/src/oc_chef_wm_org_associations.erl user org associations + +// Association represents the response from creating an invitation to join an organization +// POST /organization/NAME/association_requests type Association struct { Uri string `json:"uri"` // the last part of the uri is the invitation id OrganizationUser struct { @@ -24,47 +27,60 @@ type Association struct { } `json:"user"` } -type OrgUserListEntry struct { - User struct { - Username string `json:"username,omitempty"` - } `json:"user,omitempty"` -} - -type OrgUser struct { - Username string `json:"username,omitempty"` - Email string `json:"email,omitempty"` - DisplayName string `json:"display_name,omitempty"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - PublicKey string `json:"public_key,omitempty"` -} - +// RescindInvite respresents the response from deleting an invitation +// DELETE /organization/NAME/association_requests/ID type RescindInvite struct { Id string `json:"id,omitempty"` Orgname string `json:"orgname,omitempty"` Username string `json:"username,omitempty"` } +// Invite represents an entry in the array of responses listing the outstanding invitations +// GET /organization/NAME/association_requests type Invite struct { Id string `json:"id,omitempty"` UserName string `json:"username,omitempty"` } +// Request represents the body of the request to invite a user to an organization +// POST /organization/NAME/association_requests type Request struct { User string `json:"user"` } +// AddNow represents the body of the request to add a user to an organization +// POST /organization/NAME/users type AddNow struct { Username string `json:"username"` } -// Get gets a list of the pending invitations for an organization. +// Invite represents an entry in the array of responses listing the users in an organization +// GET /organization/NAME/association_requests +type OrgUserListEntry struct { + User struct { + Username string `json:"username,omitempty"` + } `json:"user,omitempty"` +} + +// OrgUser represents the detailed information about a user in an organization +// GET /organization/NAME/user/NAME +// DELETE /organization/NAME/user/NAME +type OrgUser struct { + Username string `json:"username,omitempty"` + Email string `json:"email,omitempty"` + DisplayName string `json:"display_name,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + PublicKey string `json:"public_key,omitempty"` +} + +// ListInvites gets a list of the pending invitations for an organization. func (e *AssociationService) ListInvites() (invitelist []Invite, err error) { err = e.client.magicRequestDecoder("GET", "association_requests", nil, &invitelist) return } -// Creates an invitation for a user to join an organization on the chef server +// Invite creates an invitation for a user to join an organization on the chef server func (e *AssociationService) Invite(invite Request) (data Association, err error) { body, err := JSONReader(invite) if err != nil { @@ -74,7 +90,7 @@ func (e *AssociationService) Invite(invite Request) (data Association, err error return } -// Delete removes a pending invitation to an organization +// DeleteInvite removes a pending invitation to an organization func (e *AssociationService) DeleteInvite(id string) (rescind RescindInvite, err error) { err = e.client.magicRequestDecoder("DELETE", "association_requests/"+id, nil, &rescind) return @@ -110,7 +126,7 @@ func (e *AssociationService) AcceptInvite(id string) (data string, err error) { return } -// Get a list of the users in an organization +// List gets a list of the users in an organization func (e *AssociationService) List() (data []OrgUserListEntry, err error) { err = e.client.magicRequestDecoder("GET", "users", nil, &data) return diff --git a/association_test.go b/association_test.go new file mode 100644 index 0000000..a396629 --- /dev/null +++ b/association_test.go @@ -0,0 +1,184 @@ +package chef + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +// var ( +// testNodeJSON = "test/node.json" +// ) + +// func TestNodeFromJSONDecoder(t *testing.T) { +// if file, err := os.Open(testNodeJSON); err != nil { +// t.Error("unexpected error", err, "during os.Open on", testNodeJSON) +// } else { +// dec := json.NewDecoder(file) +// var n Node +// if err := dec.Decode(&n); err == io.EOF { +// log.Println(n) +// } else if err != nil { +// log.Fatal(err) +// } +// } +// } + +func TestAssociationMethods(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/association_requests", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + fmt.Fprintf(w, `[{"id":"1f", "username":"jollystranger"}, {"id":"2b", "username":"fredhamlet"}]`) + case r.Method == "POST": + fmt.Fprintf(w, `{ "uri": "http://chef/organizations/test/association_requests/1a" }`) + } + }) + + mux.HandleFunc("/association_requests/1f", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + fmt.Fprintf(w, `[ + { "user": {"username": "jollystranger"} + ]`) + case r.Method == "DELETE": + fmt.Fprintf(w, `{ + "id": "1f", + "orgname": "test", + "username": "jollystranger" + }`) + } + }) + + // test Invite - Invite a user + request := Request{ + User: "jollystranger", + } + invite, err := client.Associations.Invite(request) + if err != nil { + t.Errorf("Associations.Invite returned error: %v", err) + } + associationWant := Association{ + Uri: "http://chef/organizations/test/association_requests/1a", + } + if !reflect.DeepEqual(invite, associationWant) { + t.Errorf("Associations.Invite returned %+v, want %+v", invite, associationWant) + } + + // test ListInvites - return the existing invitations + invites, err := client.Associations.ListInvites() + if err != nil { + t.Errorf("Associations.List returned error: %v", err) + } + listWant := []Invite{ + Invite{Id: "1f", UserName: "jollystranger"}, + Invite{Id: "2b", UserName: "fredhamlet"}, + } + if !reflect.DeepEqual(invites, listWant) { + t.Errorf("Associations.InviteList returned %+v, want %+v", invites, listWant) + } + + // test DeleteInvite + deleted, err := client.Associations.DeleteInvite("1f") + if err != nil { + t.Errorf("Associations.Get returned error: %v", err) + } + wantInvite := RescindInvite{ + Id: "1f", + Orgname: "test", + Username: "jollystranger", + } + if !reflect.DeepEqual(deleted, wantInvite) { + t.Errorf("Associations.RescindInvite returned %+v, want %+v", deleted, wantInvite) + } + + // test InviteId + id, err := client.Associations.InviteId("fredhamlet") + if err != nil { + t.Errorf("Associations.InviteId returned error: %s", err.Error()) + } + wantId := "2b" + if !reflect.DeepEqual(id, wantId) { + t.Errorf("Associations.InviteId returned %+v, want %+v", id, wantId) + } +} + +func TestOrgUserMethods(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + // []OrgUserListEntry + fmt.Fprintf(w, `[{ "user": {"username": "jollystranger"}}]`) + case r.Method == "POST": + // err only + } + }) + + mux.HandleFunc("/users/jollystranger", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + // OrgUser + fmt.Fprintf(w, `{ "username": "jollystranger", "email": "jolly.stranger@domain.io", "display_name":"jolly" }`) + case r.Method == "DELETE": + // OrgUser + fmt.Fprintf(w, `{ "username": "jollystranger", "email": "jolly.stranger@domain.io", "display_name":"jolly" }`) + } + }) + + // test List the users + users, err := client.Associations.List() + if err != nil { + t.Errorf("Associations.List returned error: %v", err) + } + var a struct { + Username string `json:"username,omitempty"` + } + a.Username = "jollystranger" + var wuser OrgUserListEntry + wuser.User = a + wantUsers := []OrgUserListEntry{ + wuser, + } + if !reflect.DeepEqual(users, wantUsers) { + t.Errorf("Associations.List returned %+v, want %+v", users, wantUsers) + } + + // test Add user + addme := AddNow{ + Username: "jollystranger", + } + err = client.Associations.Add(addme) + if err != nil { + t.Errorf("Associations.Add returned error: %v", err) + } + + // test Get user details + user, err := client.Associations.Get("jollystranger") + if err != nil { + t.Errorf("Associations.Get returned error: %v", err) + } + wantUser := OrgUser{ + Username: "jollystranger", Email: "jolly.stranger@domain.io", DisplayName: "jolly", + } + if !reflect.DeepEqual(user, wantUser) { + t.Errorf("Associations.Get returned %+v, want %+v", user, wantUser) + } + + // test Delete user details + delu, err := client.Associations.Delete("jollystranger") + if err != nil { + t.Errorf("Associations.Delete returned error: %v", err) + } + delUser := OrgUser{ + Username: "jollystranger", Email: "jolly.stranger@domain.io", DisplayName: "jolly", + } + if !reflect.DeepEqual(delu, delUser) { + t.Errorf("Associations.Delete returned %+v, want %+v", delu, delUser) + } +} From f5e4bdcd2eec54257870e5b4534766d69ddfc1d4 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Fri, 10 Jan 2020 21:25:15 -0800 Subject: [PATCH 41/65] Add support for the license end point --- authenticate_test.go | 6 +- .../default/go/src/chefapi_test/bin/license | 10 +++ .../src/chefapi_test/cmd/license/license.go | 22 +++++++ .../chefapi_examples/recipes/chef_objects.rb | 2 +- examples/chefapi_examples/recipes/chefapi.rb | 1 - .../default/inspec/authenticate.rb | 2 +- .../integration/default/inspec/license.rb | 8 +++ http.go | 30 +++++---- license.go | 21 ++++++ license_test.go | 66 +++++++++++++++++++ test/license.json | 6 ++ user.go | 14 ++-- 12 files changed, 161 insertions(+), 27 deletions(-) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/license/license.go create mode 100644 examples/chefapi_examples/test/integration/default/inspec/license.rb create mode 100644 license.go create mode 100644 license_test.go create mode 100644 test/license.json diff --git a/authenticate_test.go b/authenticate_test.go index 34142e4..417a005 100644 --- a/authenticate_test.go +++ b/authenticate_test.go @@ -2,12 +2,12 @@ package chef import ( "encoding/json" + "github.com/stretchr/testify/assert" "io" "log" "net/http" "os" "testing" - "github.com/stretchr/testify/assert" ) var ( @@ -50,8 +50,8 @@ func TestAuthenticatesCreate(t *testing.T) { request.Password = "invalid" err := client.AuthenticateUser.Authenticate(request) if assert.NotNil(t, err) { - assert.Contains(t, err.Error(), "401") - } + assert.Contains(t, err.Error(), "401") + } request.UserName = "user1" request.Password = "password" diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license new file mode 100755 index 0000000..8eece2f --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license @@ -0,0 +1,10 @@ +#!/bin/bash + +# License testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds + +go run ${BASE}/../cmd/license/license.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/license/license.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/license/license.go new file mode 100644 index 0000000..ef79f93 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/license/license.go @@ -0,0 +1,22 @@ +// +// Test the go-chef/chef chef server api /license endpoints against a live server +// +package main + +import ( + "fmt" + "chefapi_test/testapi" +) + + +// main Exercise the chef server api +func main() { + // Create a client for access + client := testapi.Client() + + license, err := client.License.Get() + if err != nil { + fmt.Println("Issue getting license information", err) + } + fmt.Printf("List license: %+v", license) +} diff --git a/examples/chefapi_examples/recipes/chef_objects.rb b/examples/chefapi_examples/recipes/chef_objects.rb index 847e366..c96bdc5 100644 --- a/examples/chefapi_examples/recipes/chef_objects.rb +++ b/examples/chefapi_examples/recipes/chef_objects.rb @@ -18,7 +18,7 @@ execute 'get the ssl certificate for the chef server' do command 'knife ssl fetch' - not_if { File.exist? '/root/.chef/trusted_certs/testhost' } + not_if { ::File.exist? '/root/.chef/trusted_certs/testhost' } end # Register this node with the server diff --git a/examples/chefapi_examples/recipes/chefapi.rb b/examples/chefapi_examples/recipes/chefapi.rb index ae3a0e0..a890d17 100644 --- a/examples/chefapi_examples/recipes/chefapi.rb +++ b/examples/chefapi_examples/recipes/chefapi.rb @@ -13,7 +13,6 @@ recursive true end -puts "BRANCH #{node['chefapi_examples']['go_chef_branch']}" git '/go/src/github.com/go-chef/chef' do repository 'https://github.com/go-chef/chef.git' end diff --git a/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb b/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb index 1bfb498..f739bcf 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/authenticate.rb @@ -4,5 +4,5 @@ describe command('/go/src/chefapi_test/bin/authenticate') do its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } its('stdout') { should match(/^Authenticate with a valid password \/) } - its('stdout') { should match(/^Authenticate with an invalid password POST https:\/\/localhost\/authenticate_user: 401/) } + its('stdout') { should match(%r{^Authenticate with an invalid password POST https://localhost/authenticate_user: 401}) } end diff --git a/examples/chefapi_examples/test/integration/default/inspec/license.rb b/examples/chefapi_examples/test/integration/default/inspec/license.rb new file mode 100644 index 0000000..43649c0 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/license.rb @@ -0,0 +1,8 @@ +# Inspec tests for the license chef api go module +# + +describe command('/go/src/chefapi_test/bin/license') do + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } + its('stderr') { should_not match(/Issue/) } + its('stdout') { should match(%r{^List license: {LimitExceeded:false NodeLicense:25 NodeCount:0 UpgradeUrl:http://www.chef.io/contact/on-premises-simple}}) } +end diff --git a/http.go b/http.go index 668be5e..6ebd69c 100644 --- a/http.go +++ b/http.go @@ -40,21 +40,22 @@ type Client struct { BaseURL *url.URL client *http.Client - ACLs *ACLService - Associations *AssociationService + ACLs *ACLService + Associations *AssociationService AuthenticateUser *AuthenticateUserService - Clients *ApiClientService - Cookbooks *CookbookService - DataBags *DataBagService - Environments *EnvironmentService - Groups *GroupService - Nodes *NodeService - Organizations *OrganizationService - Principals *PrincipalService - Roles *RoleService - Sandboxes *SandboxService - Search *SearchService - Users *UserService + Clients *ApiClientService + Cookbooks *CookbookService + DataBags *DataBagService + Environments *EnvironmentService + Groups *GroupService + License *LicenseService + Nodes *NodeService + Organizations *OrganizationService + Principals *PrincipalService + Roles *RoleService + Sandboxes *SandboxService + Search *SearchService + Users *UserService } // Config contains the configuration options for a chef client. This structure is used primarily in the NewClient() constructor in order to setup a proper client object @@ -163,6 +164,7 @@ func NewClient(cfg *Config) (*Client, error) { c.DataBags = &DataBagService{client: c} c.Environments = &EnvironmentService{client: c} c.Groups = &GroupService{client: c} + c.License = &LicenseService{client: c} c.Nodes = &NodeService{client: c} c.Organizations = &OrganizationService{client: c} c.Principals = &PrincipalService{client: c} diff --git a/license.go b/license.go new file mode 100644 index 0000000..9df09e0 --- /dev/null +++ b/license.go @@ -0,0 +1,21 @@ +package chef + +type LicenseService struct { + client *Client +} + +// License represents the body of the returned information. +type License struct { + LimitExceeded bool `json:"limit_exceeded"` + NodeLicense int `json:"node_license"` + NodeCount int `json:"node_count"` + UpgradeUrl string `json:"Upgrade_url"` +} + +// License gets license information. +// +// https://docs.chef.io/api_chef_server.html#license +func (e *LicenseService) Get() (data License, err error) { + err = e.client.magicRequestDecoder("GET", "license", nil, &data) + return +} diff --git a/license_test.go b/license_test.go new file mode 100644 index 0000000..4acb9c2 --- /dev/null +++ b/license_test.go @@ -0,0 +1,66 @@ +package chef + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "reflect" + "testing" +) + +var ( + testLicenseJSON = "test/license.json" +) + +func TestLicenseFromJSONDecoder(t *testing.T) { + if file, err := os.Open(testLicenseJSON); err != nil { + t.Error("Unexpected error '", err, "' during os.Open on", testLicenseJSON) + } else { + dec := json.NewDecoder(file) + var g License + if err := dec.Decode(&g); err == io.EOF { + log.Fatal(g) + } else if err != nil { + log.Fatal(err) + } + } +} + +func TestLicenseGet(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/license", func(w http.ResponseWriter, r *http.Request) { + dec := json.NewDecoder(r.Body) + var request License + dec.Decode(&request) + switch { + case r.Method == "GET": + fmt.Fprintf(w, `{ + "limit_exceeded": false, + "node_license": 25, + "node_count": 12, + "upgrade_url": "http://www.chef.io/contact/on-premises-simple" + }`) + } + }) + + wantLicense := License{ + LimitExceeded: false, + NodeLicense: 25, + NodeCount: 12, + UpgradeUrl: "http://www.chef.io/contact/on-premises-simple", + } + + license, err := client.License.Get() + if err != nil { + t.Errorf("License.Get returned error: %s", err.Error()) + } + if !reflect.DeepEqual(license, wantLicense) { + t.Errorf("License.Get returned %+v, want %+v", license, wantLicense) + } + +} diff --git a/test/license.json b/test/license.json new file mode 100644 index 0000000..109a92c --- /dev/null +++ b/test/license.json @@ -0,0 +1,6 @@ +{ + "limit_exceeded": false, + "node_license": 25, + "node_count": 12, + "upgrade_url": "http://www.chef.io/contact/on-premises-simple" +} diff --git a/user.go b/user.go index f24acbd..a63c1f4 100644 --- a/user.go +++ b/user.go @@ -101,12 +101,12 @@ func (e *UserService) Get(name string) (user User, err error) { } // TODO: -// API /users/USERNAME GET external_authentication_uid and email filters +// API /users/USERNAME GET external_authentication_uid and email filters - filters is implemented. This may be ok. // note that the combination of verbose and filters is not supported // API /users/USERNAME GET verbose parameter -// API /users/USERNAME PUT -// API /users/USERNAME/keys GET -// API /users/USERNAME/keys POST -// API /users/USERNAME/keys/Key DELETE -// API /users/USERNAME/keys/Key GET -// API /users/USERNAME/keys/Key PUT +// API /users/USERNAME PUT issue #145 +// API /users/USERNAME/keys GET issue #130 +// API /users/USERNAME/keys POST issue #130 +// API /users/USERNAME/keys/Key DELETE issue #130 +// API /users/USERNAME/keys/Key GET issue #130 +// API /users/USERNAME/keys/Key PUT issue #130 From c5e7eec52bee3ccfc8ed61875a165fe819cbe774 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 18 Jan 2020 06:38:13 -0800 Subject: [PATCH 42/65] Point to the right repository --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index af53ce4..b28e1a5 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/chef/go-chef +module github.com/go-chef/chef go 1.12 From 4f79680ad54b21ca6ffccc2689703c40d42c15eb Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 18 Jan 2020 21:05:18 -0800 Subject: [PATCH 43/65] Add _status and universe endpoint support [minor] Add the status and organization endpoint examples --- .../default/go/src/chefapi_test/bin/status | 10 ++ .../default/go/src/chefapi_test/bin/universe | 10 ++ .../go/src/chefapi_test/cmd/status/status.go | 22 +++++ .../src/chefapi_test/cmd/universe/universe.go | 22 +++++ examples/chefapi_examples/spec/spec_helper.rb | 2 - .../spec/unit/recipes/default_spec.rb | 22 ----- .../integration/default/inspec/node_spec.rb | 2 +- .../integration/default/inspec/status_spec.rb | 7 ++ .../default/inspec/universe_spec.rb | 7 ++ go.mod | 6 +- go.sum | 11 +++ http.go | 4 + status.go | 20 ++++ status_test.go | 81 ++++++++++++++++ test/status.json | 19 ++++ universe.go | 70 +++++++++++++ universe_test.go | 97 +++++++++++++++++++ 17 files changed, 386 insertions(+), 26 deletions(-) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/status/status.go create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/universe/universe.go delete mode 100644 examples/chefapi_examples/spec/spec_helper.rb delete mode 100644 examples/chefapi_examples/spec/unit/recipes/default_spec.rb create mode 100644 examples/chefapi_examples/test/integration/default/inspec/status_spec.rb create mode 100644 examples/chefapi_examples/test/integration/default/inspec/universe_spec.rb create mode 100644 status.go create mode 100644 status_test.go create mode 100644 test/status.json create mode 100644 universe.go create mode 100644 universe_test.go diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status new file mode 100755 index 0000000..005bca3 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status @@ -0,0 +1,10 @@ +#!/bin/bash + +# Status testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds + +go run ${BASE}/../cmd/status/status.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe new file mode 100755 index 0000000..7a95810 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe @@ -0,0 +1,10 @@ +#!/bin/bash + +# Universe testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds + +go run ${BASE}/../cmd/universe/universe.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/status/status.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/status/status.go new file mode 100644 index 0000000..b88b642 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/status/status.go @@ -0,0 +1,22 @@ +// +// Test the go-chef/chef chef server api /_status endpoints against a live server +// +package main + +import ( + "fmt" + "chefapi_test/testapi" +) + + +// main Exercise the chef server api +func main() { + // Create a client for access + client := testapi.Client() + + status, err := client.Status.Get() + if err != nil { + fmt.Println("Issue getting status information", err) + } + fmt.Printf("List status: %+v", status) +} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/universe/universe.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/universe/universe.go new file mode 100644 index 0000000..6232c9f --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/universe/universe.go @@ -0,0 +1,22 @@ +// +// Test the go-chef/chef chef server api /universe endpoints against a live server +// +package main + +import ( + "fmt" + "chefapi_test/testapi" +) + + +// main Exercise the chef server api +func main() { + // Create a client for access + client := testapi.Client() + + universe, err := client.Universe.Get() + if err != nil { + fmt.Println("Issue getting universe information", err) + } + fmt.Printf("List universe: %+v", universe) +} diff --git a/examples/chefapi_examples/spec/spec_helper.rb b/examples/chefapi_examples/spec/spec_helper.rb deleted file mode 100644 index 1dd5126..0000000 --- a/examples/chefapi_examples/spec/spec_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'chefspec' -require 'chefspec/berkshelf' diff --git a/examples/chefapi_examples/spec/unit/recipes/default_spec.rb b/examples/chefapi_examples/spec/unit/recipes/default_spec.rb deleted file mode 100644 index 803ec0c..0000000 --- a/examples/chefapi_examples/spec/unit/recipes/default_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# -# Cookbook:: chefapi_examples -# Spec:: default -# -# Copyright:: 2017, The Authors, All Rights Reserved. - -require 'spec_helper' - -describe 'chefapi_examples::default' do - context 'When all attributes are default, on an Ubuntu 16.04' do - let(:chef_run) do - # for a complete list of available platforms and versions see: - # https://github.com/customink/fauxhai/blob/master/PLATFORMS.md - runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04') - runner.converge(described_recipe) - end - - it 'converges successfully' do - expect { chef_run }.to_not raise_error - end - end -end diff --git a/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb index 43583e1..3ce6fc9 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/node_spec.rb @@ -1,4 +1,4 @@ -# Inspec tests for the node chef api go module +# Inspec tests for the status chef api go module # describe command('/go/src/chefapi_test/bin/node') do diff --git a/examples/chefapi_examples/test/integration/default/inspec/status_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/status_spec.rb new file mode 100644 index 0000000..34c6d30 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/status_spec.rb @@ -0,0 +1,7 @@ +# Inspec tests for the status chef api go module +# + +describe command('/go/src/chefapi_test/bin/status') do + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } + its('stdout') { should match(/List status: \{Status:pong Upstreams:map\[(?=.*chef_solr:pong)(?=.*chef_sql:pong)(?=.*chef_index:pong)(?=.*oc_chef_action:pong)(?=.*oc_chef_authz:pong).*\].*Keygen:map\[(?=.*keys:10)(?=.*max:10)(?=.*max_workers:1)(?=.*cur_max_workers:1)(?=.*inflight:0)(?=.*avail_workers:1)(?=.*start_size:0).*\]\}/) } +end diff --git a/examples/chefapi_examples/test/integration/default/inspec/universe_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/universe_spec.rb new file mode 100644 index 0000000..09df3cd --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/universe_spec.rb @@ -0,0 +1,7 @@ +# Inspec tests for the universe chef api go module +# + +describe command('/go/src/chefapi_test/bin/universe') do + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } + its('stdout') { should match(%r{^List universe: \{Books:map\[sampbook:\{Versions:map\[(?=.*0.2.0:\{LocationPath:https:\/\/localhost\/organizations\/test\/cookbooks\/sampbook\/0.2.0 LocationType:chef_server Dependencies:map\[\]\})(?=.*0.1.0:\{LocationPath:https:\/\/localhost\/organizations\/test\/cookbooks\/sampbook\/0.1.0 LocationType:chef_server Dependencies:map\[\]\}).*\]\}\]\}}) } +end diff --git a/go.mod b/go.mod index b28e1a5..a680559 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,10 @@ require ( github.com/cenkalti/backoff v2.1.1+incompatible github.com/ctdk/goiardi v0.11.10 github.com/davecgh/go-spew v1.1.1 - github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a + github.com/google/go-cmp v0.4.0 + github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de // indirect + github.com/smartystreets/assertions v1.0.1 // indirect + github.com/smartystreets/goconvey v1.6.4 github.com/stretchr/testify v1.4.0 + gopkg.in/yaml.v2 v2.2.7 // indirect ) diff --git a/go.sum b/go.sum index 0aec861..c963745 100644 --- a/go.sum +++ b/go.sum @@ -7,16 +7,24 @@ github.com/ctdk/goiardi v0.11.10/go.mod h1:Pr6Cj6Wsahw45myttaOEZeZ0LE7p1qzWmzgsB github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de h1:F7WD09S8QB4LrkEpka0dFPLSotH11HRpCsLIbIcJ7sU= +github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= +github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -25,6 +33,9 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/http.go b/http.go index 6ebd69c..4e6b388 100644 --- a/http.go +++ b/http.go @@ -55,6 +55,8 @@ type Client struct { Roles *RoleService Sandboxes *SandboxService Search *SearchService + Status *StatusService + Universe *UniverseService Users *UserService } @@ -171,6 +173,8 @@ func NewClient(cfg *Config) (*Client, error) { c.Roles = &RoleService{client: c} c.Sandboxes = &SandboxService{client: c} c.Search = &SearchService{client: c} + c.Status = &StatusService{client: c} + c.Universe = &UniverseService{client: c} c.Users = &UserService{client: c} return c, nil } diff --git a/status.go b/status.go new file mode 100644 index 0000000..68d037c --- /dev/null +++ b/status.go @@ -0,0 +1,20 @@ +package chef + +type StatusService struct { + client *Client +} + +// Status represents the body of the returned information. +type Status struct { + Status string `json:"status"` + Upstreams map[string]string `json:"upstreams"` + Keygen map[string]int `json:"keygen"` +} + +// Status gets license information. +// +// https://docs.chef.io/api_chef_server.html#license +func (e *StatusService) Get() (data Status, err error) { + err = e.client.magicRequestDecoder("GET", "_status", nil, &data) + return +} diff --git a/status_test.go b/status_test.go new file mode 100644 index 0000000..529c9fe --- /dev/null +++ b/status_test.go @@ -0,0 +1,81 @@ +package chef + +import ( + "encoding/json" + "fmt" + // "github.com/google/go-cmp/cmp" + "io" + "log" + "net/http" + "os" + "reflect" + "testing" +) + +var ( + testStatusJSON = "test/status.json" +) + +func TestStatusFromJSONDecoder(t *testing.T) { + if file, err := os.Open(testStatusJSON); err != nil { + t.Error("Unexpected error '", err, "' during os.Open on", testStatusJSON) + } else { + dec := json.NewDecoder(file) + var g Status + if err := dec.Decode(&g); err == io.EOF { + log.Fatal(g) + } else if err != nil { + log.Fatal(err) + } + } +} + +func TestStatusGet(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/_status", func(w http.ResponseWriter, r *http.Request) { + dec := json.NewDecoder(r.Body) + var request Status + dec.Decode(&request) + switch { + case r.Method == "GET": + fmt.Fprintf(w, `{ + "status": "pong", + "upstreams": { + "chef_elasticsearch": "pong", + "chef_sql": "pong", + "chef_index": "pong", + "oc_chef_authz": "pong", + "data_collector": "pong" + }, + "keygen": { + "keys": 10, + "max": 10, + "max_workers": 2, + "cur_max_workers": 2, + "inflight": 0, + "avail_workers": 2, + "start_size": 0 + } + }`) + } + + }) + + wantStatus := Status{ + Status: "pong", + Upstreams: map[string]string{"chef_elasticsearch": "pong", "chef_index": "pong", "chef_sql": "pong", "data_collector": "pong", "oc_chef_authz": "pong"}, + Keygen: map[string]int{"avail_workers": 2, "cur_max_workers": 2, "inflight": 0, "keys": 10, "max": 10, "max_workers": 2, "start_size": 0}, + } + + status, err := client.Status.Get() + if err != nil { + t.Errorf("Status.Get returned error: %s", err.Error()) + } + + if !reflect.DeepEqual(status, wantStatus) { + t.Errorf("Status.Get returned %+v, want %+v, error %+v", status, wantStatus, err) + } + +} diff --git a/test/status.json b/test/status.json new file mode 100644 index 0000000..3768a1f --- /dev/null +++ b/test/status.json @@ -0,0 +1,19 @@ +{ + "status": "pong", + "upstreams": { + "chef_elasticsearch": "pong", + "chef_sql": "pong", + "chef_index": "pong", + "oc_chef_authz": "pong", + "data_collector": "pong" + }, + "keygen": { + "keys": 10, + "max": 10, + "max_workers": 2, + "cur_max_workers": 2, + "inflight": 0, + "avail_workers": 2, + "start_size": 0 + } +} diff --git a/universe.go b/universe.go new file mode 100644 index 0000000..279f695 --- /dev/null +++ b/universe.go @@ -0,0 +1,70 @@ +package chef + +type UniverseService struct { + client *Client +} + +// Universe represents the body of the returned information. +type Universe struct { + Books map[string]UniverseBook +} + +type UniverseBook struct { + Versions map[string]UniverseVersion +} + +type UniverseVersion struct { + LocationPath string + LocationType string + Dependencies map[string]string +} + +// Universe gets available cookbook version information. +// +// https://docs.chef.io/api_chef_server.html#universe +func (e *UniverseService) Get() (universe Universe, err error) { + var data map[string]interface{} + err = e.client.magicRequestDecoder("GET", "universe", nil, &data) + unpackUniverse(&universe, &data) + return +} + +func unpackUniverse(universe *Universe, data *map[string]interface{}) { + (*universe).Books = make(map[string]UniverseBook) + for bookn, versions := range *data { + ub := UniverseBook{} + ub.Versions = make(map[string]UniverseVersion) + switch versions.(type) { + case map[string]interface{}: + for vname, version := range versions.(map[string]interface{}) { + uv := UniverseVersion{} + switch version.(type) { + case map[string]interface{}: + for aname, attr := range version.(map[string]interface{}) { + deps := make(map[string]string) + switch aname { + case "dependencies": + for dname, dep := range attr.(map[string]interface{}) { + switch dep.(type) { + case string: + deps[dname] = dep.(string) + default: + } + } + uv.Dependencies = deps + case "location_path": + uv.LocationPath = attr.(string) + case "location_type": + uv.LocationType = attr.(string) + } + } + default: + } + ub.Versions[vname] = uv + } + default: + } + (*universe).Books[bookn] = ub + } + return +} diff --git a/universe_test.go b/universe_test.go new file mode 100644 index 0000000..9ea9135 --- /dev/null +++ b/universe_test.go @@ -0,0 +1,97 @@ +package chef + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUniverseGet(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/universe", func(w http.ResponseWriter, r *http.Request) { + dec := json.NewDecoder(r.Body) + var request Universe + dec.Decode(&request) + switch { + case r.Method == "GET": + fmt.Fprintf(w, `{ + "ffmpeg": { + "0.1.0": { + "location_path": "http://supermarket.chef.io/api/v1/cookbooks/ffmpeg/0.1.0/download", + "location_type": "supermarket", + "dependencies": { + "git": ">= 0.0.0", + "build-essential": ">= 0.0.0", + "libvpx": "~> 0.1.1", + "x264": "~> 0.1.1" + } + }, + "0.1.1": { + "location_path": "http://supermarket.chef.io/api/v1/cookbooks/ffmpeg/0.1.1/download", + "location_type": "supermarket", + "dependencies": { + "git": ">= 0.0.0", + "build-essential": ">= 0.0.0", + "libvpx": "~> 0.1.1", + "x264": "~> 0.1.1" + } + } + }, + "pssh": { + "0.1.0": { + "location_path": "http://supermarket.chef.io/api/v1/cookbooks/pssh.1.0/download", + "location_type": "supermarket", + "dependencies": {} + } + } + }`) + } + }) + + wantU := Universe{} + wantU.Books = make(map[string]UniverseBook) + ffmpeg := UniverseBook{} + ffmpeg.Versions = make(map[string]UniverseVersion) + ffmpeg.Versions["0.1.0"] = UniverseVersion{ + LocationPath: "http://supermarket.chef.io/api/v1/cookbooks/ffmpeg/0.1.0/download", + LocationType: "supermarket", + Dependencies: map[string]string{ + "git": ">= 0.0.0", + "build-essential": ">= 0.0.0", + "libvpx": "~> 0.1.1", + "x264": "~> 0.1.1", + }, + } + ffmpeg.Versions["0.1.1"] = UniverseVersion{ + LocationPath: "http://supermarket.chef.io/api/v1/cookbooks/ffmpeg/0.1.1/download", + LocationType: "supermarket", + Dependencies: map[string]string{ + "git": ">= 0.0.0", + "build-essential": ">= 0.0.0", + "libvpx": "~> 0.1.1", + "x264": "~> 0.1.1", + }, + } + pssh := UniverseBook{} + pssh.Versions = make(map[string]UniverseVersion) + pssh.Versions["0.1.0"] = UniverseVersion{ + LocationPath: "http://supermarket.chef.io/api/v1/cookbooks/pssh.1.0/download", + LocationType: "supermarket", + Dependencies: map[string]string{}, + } + wantU.Books["ffmpeg"] = ffmpeg + wantU.Books["pssh"] = pssh + + universe, err := client.Universe.Get() + if err != nil { + t.Errorf("Universe.Get returned error: %s", err.Error()) + } + + if !reflect.DeepEqual(universe, wantU) { + t.Errorf("Universe.Get returned %+v, want %+v", universe, wantU) + } +} From 1376ba249bb5fd032e7f623b86038ddcf5bb71f3 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 22 Feb 2020 18:49:20 -0800 Subject: [PATCH 44/65] Add a debug_on function and print server responses in debug mode Minor clean up --- association_test.go | 18 ------------------ debug.go | 6 ++++++ http.go | 4 +++- release.go | 4 ++++ 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/association_test.go b/association_test.go index a396629..8a82dd4 100644 --- a/association_test.go +++ b/association_test.go @@ -7,24 +7,6 @@ import ( "testing" ) -// var ( -// testNodeJSON = "test/node.json" -// ) - -// func TestNodeFromJSONDecoder(t *testing.T) { -// if file, err := os.Open(testNodeJSON); err != nil { -// t.Error("unexpected error", err, "during os.Open on", testNodeJSON) -// } else { -// dec := json.NewDecoder(file) -// var n Node -// if err := dec.Decode(&n); err == io.EOF { -// log.Println(n) -// } else if err != nil { -// log.Fatal(err) -// } -// } -// } - func TestAssociationMethods(t *testing.T) { setup() defer teardown() diff --git a/debug.go b/debug.go index d59297c..42360aa 100644 --- a/debug.go +++ b/debug.go @@ -1,5 +1,7 @@ // +build debug +// Add -tags debug to go run or go build to turn on debug output + package chef import "log" @@ -7,3 +9,7 @@ import "log" func debug(fmt string, args ...interface{}) { log.Printf(fmt, args...) } + +func debug_on() bool { + return true +} diff --git a/http.go b/http.go index 4e6b388..cd51631 100644 --- a/http.go +++ b/http.go @@ -265,9 +265,11 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { io.Copy(w, restee) } else { err = json.NewDecoder(restee).Decode(v) - if err != nil { + if debug_on() { resbody, _ := ioutil.ReadAll(&resbuf) debug("Response body: %+v\n", string(resbody)) + } + if err != nil { return res, err } } diff --git a/release.go b/release.go index feaec67..f081cef 100644 --- a/release.go +++ b/release.go @@ -4,3 +4,7 @@ package chef func debug(fmt string, args ...interface{}) { } + +func debug_on() bool { + return false +} From 07c915cb3b8b17ab83222833d6d4f5c328acf9fd Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 22 Feb 2020 20:23:00 -0800 Subject: [PATCH 45/65] Add the verbose examples --- .../go/src/chefapi_test/cmd/user/user.go | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go index 9f5c60c..9d964d4 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go @@ -34,11 +34,8 @@ func main() { userList = listUsers(client, "email=user1@domain.io") fmt.Printf("Filter users %+v\n", userList) - // userVerboseOut := listUsersVerbose(client) - // fmt.Printf("Verbose out %v\n", userVerboseOut) - - // err := client.AuthenticateUser.Authenticate(chef.AuthenticateUser{ UserName: usr1, Password: "Logn12ComplexPwd#" }) - // fmt.Println("Authenticate usr1 ", err) + userVerboseOut := listUsersVerbose(client) + fmt.Printf("Verbose out %v\n", userVerboseOut) userResult = createUser(client, usr1) fmt.Printf("Add user1 again %+v\n", userResult) @@ -52,12 +49,12 @@ func main() { userList = listUsers(client) fmt.Printf("List after adding %+v EndAddList\n", userList) - // userbody := chef.User{ FullName: "usr1new" } - // userresult := updateUser(client, "usr1", userbody) - // fmt.Printf("Update user1 %+v", userresult) + userbody := chef.User{ FullName: "usr1new" } + userresult := updateUser(client, "usr1", userbody) + fmt.Printf("Update user1 %+v", userresult) - // userout = getUser(client, "usr1") - // fmt.Println("Get usr1 after update %+v\n", userout) + userout = getUser(client, "usr1") + fmt.Println("Get usr1 after update %+v\n", userout) err := deleteUser(client, "usr1") fmt.Println("Delete usr1", err) @@ -97,7 +94,7 @@ func getUser(client *chef.Client, name string) chef.User { func listUsers(client *chef.Client, filters ...string) map[string]string { var filter string if len(filters) > 0 { - filter = filters[0] + filter = filters[0] } userList, err := client.Users.List(filter) if err != nil { @@ -107,21 +104,20 @@ func listUsers(client *chef.Client, filters ...string) map[string]string { } // listUsersVerbose uses the chef server api to list all users and return verbose output -//func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseItem { -// userList, err := client.Users.ListVerbose() - // fmt.Println("VERBOSE LIST", userList) -// if err != nil { -// fmt.Println("Issue listing verbose users:", err) -// } -// return userList -//} +func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseItem { + userList, err := client.Users.ListVerbose() + fmt.Println("VERBOSE LIST", userList) + if err != nil { + fmt.Println("Issue listing verbose users:", err) + } + return userList +} // updateUser uses the chef server api to update information for a single user -// func updateUser(client *chef.Client, username string, user chef.User) chef.User { -// user_update, err := client.Users.Update(username, user) - //if err != nil { - //i fmt.Println("Issue updating user:", err) - // -//} -// return user_update -//} +func updateUser(client *chef.Client, username string, user chef.User) chef.User { + user_update, err := client.Users.Update(username, user) + if err != nil { + fmt.Println("Issue updating user:", err) + } + return user_update +} From 9aa825116d9e705156e4267e19c2f05febf17f10 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 14 Oct 2019 15:01:57 -0700 Subject: [PATCH 46/65] Add a way to list users with the verbose option --- user.go | 23 ++++++++++++++++++++++- user_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/user.go b/user.go index a63c1f4..7356ec7 100644 --- a/user.go +++ b/user.go @@ -29,6 +29,12 @@ type UserResult struct { PrivateKey string `json:"private_key,omitempty"` } +type UserVerboseResult struct { + Email string `json:"email,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` +} + type UserKey struct { KeyName string `json:"name,omitempty"` PublicKey string `json:"public_key,omitempty"` @@ -43,7 +49,7 @@ type UserKeyResult struct { // /users GET // List lists the users in the Chef server. -// +// // Chef API docs: https://docs.chef.io/api_chef_server.html#users func (e *UserService) List(filters ...string) (userlist map[string]string, err error) { url := "users" @@ -54,6 +60,21 @@ func (e *UserService) List(filters ...string) (userlist map[string]string, err e return } +// /users GET +// List lists the users in the Chef server in verbose format. +// +// Chef API docs: https://docs.chef.io/api_chef_server.html#users +func (e *UserService) VerboseList(filters ...string) (userlist map[string]UserVerboseResult, err error) { + url := "users" + filters = append(filters, "verbose=true") + if len(filters) > 0 { + url += "?" + strings.Join(filters, "&") + } + userlist = make(map[string]UserVerboseResult) + err = e.client.magicRequestDecoder("GET", url, nil, &userlist) + return +} + // /users POST // Creates a User on the chef server // 201 = sucesss diff --git a/user_test.go b/user_test.go index 163f5f0..678f489 100644 --- a/user_test.go +++ b/user_test.go @@ -13,6 +13,7 @@ import ( var ( testUserJSON = "test/user.json" + testVerboseUserJSON = "test/verbose_user.json" ) func TestUserFromJSONDecoder(t *testing.T) { @@ -29,6 +30,20 @@ func TestUserFromJSONDecoder(t *testing.T) { } } +func TestVerboseUserFromJSONDecoder(t *testing.T) { + if file, err := os.Open(testVerboseUserJSON); err != nil { + t.Error("Unexpected error '", err, "' during os.Open on", testVerboseUserJSON) + } else { + dec := json.NewDecoder(file) + var g User + if err := dec.Decode(&g); err == io.EOF { + log.Fatal(g) + } else if err != nil { + log.Fatal(err) + } + } +} + func TestUserslist(t *testing.T) { setup() defer teardown() @@ -51,6 +66,34 @@ func TestUserslist(t *testing.T) { } } +func TestVerboseUserslist(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + fmt.Fprintf(w, `{ + "janechef": { "email": "jane.chef@user.com", "first_name": "jane", "last_name": "chef_user" }, + "yaelsmith": { "email": "yael.chef@user.com", "first_name": "yael", "last_name": "smith" } + }`) + + } + }) + + // Test list + users, err := client.Users.VerboseList() + if err != nil { + t.Errorf("Verbose Users.List returned error: %v", err) + } + jane := UserVerboseResult{ Email: "jane.chef@user.com", FirstName: "jane", LastName: "chef_user" } + yael := UserVerboseResult{ Email: "yael.chef@user.com", FirstName: "yael", LastName: "smith" } + listWant := map[string]UserVerboseResult{ "janechef": jane, "yaelsmith": yael } + if !reflect.DeepEqual(users, listWant) { + t.Errorf("Verbose Users.List returned %+v, want %+v", users, listWant) + } +} + func TestUserCreate(t *testing.T) { setup() defer teardown() From 22fdca986e62690a74a567ad12ab2dba1863930e Mon Sep 17 00:00:00 2001 From: markgibbons Date: Mon, 11 Nov 2019 20:06:41 -0800 Subject: [PATCH 47/65] Add test data for the verbose user method --- test/verbose_user.json | 4 ++++ user.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 test/verbose_user.json diff --git a/test/verbose_user.json b/test/verbose_user.json new file mode 100644 index 0000000..692df9d --- /dev/null +++ b/test/verbose_user.json @@ -0,0 +1,4 @@ +{ + "janechef": { "email": "jane.chef@user.com", "first_name": "jane", "last_name": "chef_user" }, + "yaelsmith": { "email": "yeal.chef@user.com", "first_name": "yeal", "last_name": "smith" } +} diff --git a/user.go b/user.go index 7356ec7..a96a789 100644 --- a/user.go +++ b/user.go @@ -88,7 +88,7 @@ func (e *UserService) Create(user User) (data UserResult, err error) { body, err := JSONReader(user) if err != nil { return - } +./hugin_config.go } err = e.client.magicRequestDecoder("POST", "users", body, &data) return From 986bb582f1aacecd4772909d9c1179cca71c37d9 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 22 Feb 2020 21:01:16 -0800 Subject: [PATCH 48/65] Fix UserVerboseResult list, it is a map --- .../files/default/go/src/chefapi_test/cmd/user/user.go | 6 +++--- user.go | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go index 9d964d4..f068383 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go @@ -34,7 +34,7 @@ func main() { userList = listUsers(client, "email=user1@domain.io") fmt.Printf("Filter users %+v\n", userList) - userVerboseOut := listUsersVerbose(client) + userV../cmd/user/user.goerboseOut := listUsersVerbose(client) fmt.Printf("Verbose out %v\n", userVerboseOut) userResult = createUser(client, usr1) @@ -104,9 +104,9 @@ func listUsers(client *chef.Client, filters ...string) map[string]string { } // listUsersVerbose uses the chef server api to list all users and return verbose output -func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseItem { +func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseResult { userList, err := client.Users.ListVerbose() - fmt.Println("VERBOSE LIST", userList) + fmt.Printf("VERBOSE LIST %+v\n", userList) if err != nil { fmt.Println("Issue listing verbose users:", err) } diff --git a/user.go b/user.go index a96a789..c665f5f 100644 --- a/user.go +++ b/user.go @@ -70,7 +70,6 @@ func (e *UserService) VerboseList(filters ...string) (userlist map[string]UserVe if len(filters) > 0 { url += "?" + strings.Join(filters, "&") } - userlist = make(map[string]UserVerboseResult) err = e.client.magicRequestDecoder("GET", url, nil, &userlist) return } @@ -88,7 +87,7 @@ func (e *UserService) Create(user User) (data UserResult, err error) { body, err := JSONReader(user) if err != nil { return -./hugin_config.go } + } err = e.client.magicRequestDecoder("POST", "users", body, &data) return From 051c968ee690b841b0cd9556a6e7f39f85736f67 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 22 Feb 2020 21:46:36 -0800 Subject: [PATCH 49/65] Add a working /users GET verbose=true endpoint --- .../go/src/chefapi_test/cmd/user/user.go | 27 ++++++++++--------- .../integration/default/inspec/user_spec.rb | 3 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go index f068383..5f62d59 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go @@ -34,7 +34,7 @@ func main() { userList = listUsers(client, "email=user1@domain.io") fmt.Printf("Filter users %+v\n", userList) - userV../cmd/user/user.goerboseOut := listUsersVerbose(client) + userVerboseOut := listUsersVerbose(client) fmt.Printf("Verbose out %v\n", userVerboseOut) userResult = createUser(client, usr1) @@ -49,9 +49,9 @@ func main() { userList = listUsers(client) fmt.Printf("List after adding %+v EndAddList\n", userList) - userbody := chef.User{ FullName: "usr1new" } - userresult := updateUser(client, "usr1", userbody) - fmt.Printf("Update user1 %+v", userresult) + //userbody := chef.User{ FullName: "usr1new" } + //userresult := updateUser(client, "usr1", userbody) + //fmt.Printf("Update user1 %+v", userresult) userout = getUser(client, "usr1") fmt.Println("Get usr1 after update %+v\n", userout) @@ -104,8 +104,8 @@ func listUsers(client *chef.Client, filters ...string) map[string]string { } // listUsersVerbose uses the chef server api to list all users and return verbose output -func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseResult { - userList, err := client.Users.ListVerbose() +func listUsersVerbose(client *chef.Client) map[string]chef.UserVerboseResult { + userList, err := client.Users.VerboseList() fmt.Printf("VERBOSE LIST %+v\n", userList) if err != nil { fmt.Println("Issue listing verbose users:", err) @@ -113,11 +113,12 @@ func listUsersVerbose(client *chef.Client) map[string]chef.UsersVerboseResult { return userList } + // updateUser uses the chef server api to update information for a single user -func updateUser(client *chef.Client, username string, user chef.User) chef.User { - user_update, err := client.Users.Update(username, user) - if err != nil { - fmt.Println("Issue updating user:", err) - } - return user_update -} +//func updateUser(client *chef.Client, username string, user chef.User) chef.User { + //user_update, err := client.Users.Update(username, user) + //if err != nil { + //fmt.Println("Issue updating user:", err) + //} + //return user_update +//} diff --git a/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb index 3153c08..25adcd1 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb @@ -7,8 +7,7 @@ its('stdout') { should match(%r{^List initial users map\[(?=.*pivotal:https://localhost/users/pivotal).*\] EndInitialList}) } its('stdout') { should match(%r{^Add usr1 \{https://localhost/users/usr1 -----BEGIN RSA}) } its('stdout') { should match(%r{^Filter users map\[usr1:https://localhost/users/usr1\]}) } - # its('stdout') { should match(%r{^Verbose out }) } - # its('stdout') { should match(%r{^Authenticate user1 }) } + its('stdout') { should match(/^Verbose out map\[(?=.*pivotal:)/) } its('stdout') { should match(/^Get usr1 \{UserName:usr1 DisplayName:User1 Fullname Email:user1@domain.io ExternalAuthenticationUid: FirstName:user1 FullName: LastName:fullname MiddleName: Password: PublicKey:-----BEGIN/) } its('stdout') { should match(/^Pivotal user \{UserName:pivotal DisplayName:Chef Server Superuser Email:root@localhost.localdomain ExternalAuthenticationUid: FirstName:Chef FullName: LastName:Server MiddleName: Password: PublicKey:-----BEGIN/) } its('stdout') { should match(%r{^List after adding map\[(?=.*pivotal:https://localhost/users/pivotal)(?=.*usr1:https://localhost/users/usr1).*\] EndAddList}) } From e0ffda2792a889291ef8fbb9dde5e4304515ba75 Mon Sep 17 00:00:00 2001 From: Tomoya Kabe Date: Thu, 5 Mar 2020 02:05:11 +0900 Subject: [PATCH 50/65] Accept TLS certificates for Chef server --- http.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/http.go b/http.go index cd51631..f7bab13 100644 --- a/http.go +++ b/http.go @@ -74,6 +74,9 @@ type Config struct { // When set to false (default) this will enable SSL Cert Verification. If you need to disable Cert Verification set to true SkipSSL bool + // RootCAs is a reference to x509.CertPool for TLS + RootCAs *x509.CertPool + // Time to wait in seconds before giving up on a request to the server Timeout int } @@ -137,13 +140,17 @@ func NewClient(cfg *Config) (*Client, error) { baseUrl, _ := url.Parse(cfg.BaseURL) + tlsConfig := &tls.Config{InsecureSkipVerify: cfg.SkipSSL} + if cfg.RootCAs != nil { + tlsConfig.RootCAs = cfg.RootCAs + } tr := &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).Dial, - TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.SkipSSL}, + TLSClientConfig: tlsConfig, TLSHandshakeTimeout: 10 * time.Second, } From 94b8be8b1e14212f9410b1b5de8607a53172a02c Mon Sep 17 00:00:00 2001 From: Tomoya Kabe Date: Fri, 6 Mar 2020 18:29:43 +0900 Subject: [PATCH 51/65] Add TLS certificate validation test --- http_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/http_test.go b/http_test.go index e3dc59a..056a47d 100644 --- a/http_test.go +++ b/http_test.go @@ -139,6 +139,10 @@ func createServer() *httptest.Server { return httptest.NewServer(http.HandlerFunc(checkHeader)) } +func createTLSServer() *httptest.Server { + return httptest.NewTLSServer(http.HandlerFunc(checkHeader)) +} + // publicKeyFromString parses an RSA public key from a string func publicKeyFromString(key []byte) (*rsa.PublicKey, error) { block, _ := pem.Decode(key) @@ -392,6 +396,69 @@ func TestRequestToEndpoint(t *testing.T) { } } +func TestTLSValidation(t *testing.T) { + ac, err := makeAuthConfig() + if err != nil { + panic(err) + } + // Self-signed server + server := createTLSServer() + defer server.Close() + + // Without RootCAs, TLS validation should fail + chefClient, _ := NewClient(&Config{ + Name: userid, + Key: privateKey, + BaseURL: server.URL, + }) + + request, err := chefClient.NewRequest("GET", server.URL, nil) + err = ac.SignRequest(request) + if err != nil { + t.Fatal("failed to generate RequestHeaders") + } + + client := chefClient.client + response, err := client.Do(request) + if err == nil { + t.Fatal("Request should fail due to TLS certification validation failure") + } + + // Success with RootCAs containing the server's certificate + certPool := x509.NewCertPool() + certPool.AddCert(server.Certificate()) + chefClient, _ = NewClient(&Config{ + Name: userid, + Key: privateKey, + BaseURL: server.URL, + RootCAs: certPool, + }) + + request, err = chefClient.NewRequest("GET", server.URL, nil) + err = ac.SignRequest(request) + if err != nil { + t.Fatal("failed to generate RequestHeaders") + } + + client = chefClient.client + response, err = client.Do(request) + if err != nil { + t.Fatal(err) + } + + if response.StatusCode != 200 { + t.Error("Non 200 return code: " + response.Status) + } + + buf := new(bytes.Buffer) + buf.ReadFrom(response.Body) + bodyStr := buf.String() + + if bodyStr != "" { + t.Error(bodyStr) + } +} + // More Goiardi <3 func assembleSignedHeader(r *http.Request) (string, error) { sHeadStore := make(map[int]string) From f9febeeb5f42de51bb264439c46942c270a3d66d Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 7 Mar 2020 20:31:48 -0800 Subject: [PATCH 52/65] Add the local chef server certificates --- .../go/src/chefapi_test/testapi/testapi.go | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go index b1365ab..1c1230e 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go @@ -4,6 +4,7 @@ package testapi import ( + "crypto/x509" "fmt" "io/ioutil" "os" @@ -20,14 +21,15 @@ func Client() *chef.Client { chefurl := os.Args[3] skipssl, err := strconv.ParseBool(os.Args[4]) if err != nil { - skipssl = true - } + skipssl = true + } - // Create a client for access + // Create a client for access return buildClient(user, keyfile, chefurl, skipssl) } // buildClient creates a connection to a chef server using the chef api. +// goiardi uses port 4545 by default, chef-zero uses 8889, chef-server uses 443 func buildClient(user string, keyfile string, baseurl string, skipssl bool) *chef.Client { key := clientKey(keyfile) client, err := chef.NewClient(&chef.Config{ @@ -35,8 +37,9 @@ func buildClient(user string, keyfile string, baseurl string, skipssl bool) *che Key: string(key), BaseURL: baseurl, SkipSSL: skipssl, - // goiardi uses port 4545 by default, chef-zero uses 8889, chef-server uses 443 + RootCAs: chefCerts(), }) + if err != nil { fmt.Fprintln(os.Stderr, "Issue setting up client:", err) os.Exit(1) @@ -53,3 +56,23 @@ func clientKey(filepath string) string { } return string(key) } + +// chefCerts creats a cert pool for the self signed certs +// reference https://forfuncsake.github.io/post/2017/08/trust-extra-ca-cert-in-go-app/ +func chefCerts() CertPool { + const localCertFile = "/var/opt/opscode/nginx/ca/testhost.crt" + certPool := x509.SystemCertPool() + if certPool == nil { + certPool = x509.NewCertPool + } + // Read in the cert file + certs, err := ioutil.ReadFile(localCertFile) + if err != nil { + log.Fatalf("Failed to append %q to RootCAs: %v", localCertFile, err) + } + // Append our cert to the system pool + if ok := certPool.AppendCertsFromPEM(certs); !ok { + log.Println("No certs appended, using system certs only") + } + return certPool +} From 1e1b7d412eb814f6876912540cbf2e8b42f37253 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 7 Mar 2020 22:00:30 -0800 Subject: [PATCH 53/65] Change the examples to use the self signed certificates --- examples/chefapi_examples/Berksfile.lock | 2 ++ .../files/default/go/src/chefapi_test/bin/association | 6 +++--- .../files/default/go/src/chefapi_test/bin/authenticate | 2 +- .../files/default/go/src/chefapi_test/bin/cookbook | 2 +- .../files/default/go/src/chefapi_test/bin/creds | 4 ++-- .../files/default/go/src/chefapi_test/bin/group | 2 +- .../files/default/go/src/chefapi_test/bin/license | 2 +- .../files/default/go/src/chefapi_test/bin/node | 2 +- .../files/default/go/src/chefapi_test/bin/organization | 2 +- .../files/default/go/src/chefapi_test/bin/sandbox | 2 +- .../files/default/go/src/chefapi_test/bin/search | 2 +- .../default/go/src/chefapi_test/bin/search_pagination | 2 +- .../files/default/go/src/chefapi_test/bin/status | 2 +- .../files/default/go/src/chefapi_test/bin/universe | 2 +- .../files/default/go/src/chefapi_test/bin/user | 2 +- .../default/go/src/chefapi_test/testapi/testapi.go | 5 +++-- examples/chefapi_examples/metadata.rb | 1 + examples/chefapi_examples/recipes/setup.rb | 7 ++++++- user.go | 10 +++++----- user_test.go | 8 ++++---- 20 files changed, 38 insertions(+), 29 deletions(-) diff --git a/examples/chefapi_examples/Berksfile.lock b/examples/chefapi_examples/Berksfile.lock index c250eba..0c8838e 100644 --- a/examples/chefapi_examples/Berksfile.lock +++ b/examples/chefapi_examples/Berksfile.lock @@ -10,6 +10,8 @@ GRAPH chefapi_examples (0.1.0) chef-server (>= 0.0.0) fileutils (>= 0.0.0) + jn_hosts (>= 0.0.0) line (>= 0.0.0) fileutils (1.3.1) + jn_hosts (0.0.4) line (2.5.0) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association index 2c3388c..23498b6 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association @@ -6,6 +6,6 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/association/association_setup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true -go run ${BASE}/../cmd/association/association.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true -go run ${BASE}/../cmd/association/association_cleanup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true +go run ${BASE}/../cmd/association/association_setup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false +go run ${BASE}/../cmd/association/association.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false +go run ${BASE}/../cmd/association/association_cleanup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate index 9830cfa..755acfb 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/authenticate/authenticate.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true +go run ${BASE}/../cmd/authenticate/authenticate.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook index 25ef0bd..05d8313 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/cookbook/cookbook.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true +go run ${BASE}/../cmd/cookbook/cookbook.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds index 5f9cd8f..e6afce1 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds @@ -1,5 +1,5 @@ CHEFUSER='pivotal' KEYFILE='/etc/opscode/pivotal.pem' -CHEFGLOBALURL='https://localhost/' +CHEFGLOBALURL='https://testhost/' # The trailing / on the url is critically important. The api does not work without it. -CHEFORGANIZATIONURL='https://localhost/organizations/test/' +CHEFORGANIZATIONURL='https://testhost/organizations/test/' diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group index b34af11..f83dc49 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/group/group.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true +go run ${BASE}/../cmd/group/group.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license index 8eece2f..f8a4275 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/license/license.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true +go run ${BASE}/../cmd/license/license.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node index c76f103..c0fac34 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/node/nodes.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true +go run ${BASE}/../cmd/node/nodes.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization index 259838a..9d943f7 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization @@ -10,4 +10,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/organization/organization.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true +go run ${BASE}/../cmd/organization/organization.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox index 8d791ad..eeb285d 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/sandbox/sandbox.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true +go run ${BASE}/../cmd/sandbox/sandbox.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search index ebb991d..18ae712 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/search/search.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true +go run ${BASE}/../cmd/search/search.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination index 682d899..f99012d 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/search/search_pagination.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true +go run ${BASE}/../cmd/search/search_pagination.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status index 005bca3..dddb362 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/status/status.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true +go run ${BASE}/../cmd/status/status.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe index 7a95810..b972b2b 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/universe/universe.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} true +go run ${BASE}/../cmd/universe/universe.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user index 26a58da..6836491 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/user/user.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} true +go run ${BASE}/../cmd/user/user.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go index 1c1230e..6c4290c 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go @@ -7,6 +7,7 @@ import ( "crypto/x509" "fmt" "io/ioutil" + "log" "os" "strconv" @@ -59,11 +60,11 @@ func clientKey(filepath string) string { // chefCerts creats a cert pool for the self signed certs // reference https://forfuncsake.github.io/post/2017/08/trust-extra-ca-cert-in-go-app/ -func chefCerts() CertPool { +func chefCerts() *x509.CertPool { const localCertFile = "/var/opt/opscode/nginx/ca/testhost.crt" certPool := x509.SystemCertPool() if certPool == nil { - certPool = x509.NewCertPool + certPool = x509.NewCertPool() } // Read in the cert file certs, err := ioutil.ReadFile(localCertFile) diff --git a/examples/chefapi_examples/metadata.rb b/examples/chefapi_examples/metadata.rb index de79d08..607d3e5 100644 --- a/examples/chefapi_examples/metadata.rb +++ b/examples/chefapi_examples/metadata.rb @@ -6,4 +6,5 @@ chef_version '>= 13' depends 'chef-server' depends 'fileutils' +depends 'jn_hosts' depends 'line' diff --git a/examples/chefapi_examples/recipes/setup.rb b/examples/chefapi_examples/recipes/setup.rb index f3d61c1..3137f33 100644 --- a/examples/chefapi_examples/recipes/setup.rb +++ b/examples/chefapi_examples/recipes/setup.rb @@ -1,4 +1,4 @@ -directory '/etc/chef/accepted_licenses' do +fmt.Printf("Skip SSL %+v\n", skipssl)directory '/etc/chef/accepted_licenses' do recursive true end @@ -41,3 +41,8 @@ command 'DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade' ignore_failure true end + +hosts '127.0.0.1' do + action :create + entries %w[localhost testhost] +end diff --git a/user.go b/user.go index c665f5f..57f6f76 100644 --- a/user.go +++ b/user.go @@ -30,9 +30,9 @@ type UserResult struct { } type UserVerboseResult struct { - Email string `json:"email,omitempty"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` + Email string `json:"email,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` } type UserKey struct { @@ -49,7 +49,7 @@ type UserKeyResult struct { // /users GET // List lists the users in the Chef server. -// +// // Chef API docs: https://docs.chef.io/api_chef_server.html#users func (e *UserService) List(filters ...string) (userlist map[string]string, err error) { url := "users" @@ -66,7 +66,7 @@ func (e *UserService) List(filters ...string) (userlist map[string]string, err e // Chef API docs: https://docs.chef.io/api_chef_server.html#users func (e *UserService) VerboseList(filters ...string) (userlist map[string]UserVerboseResult, err error) { url := "users" - filters = append(filters, "verbose=true") + filters = append(filters, "verbose=true") if len(filters) > 0 { url += "?" + strings.Join(filters, "&") } diff --git a/user_test.go b/user_test.go index 678f489..ed74ad8 100644 --- a/user_test.go +++ b/user_test.go @@ -12,7 +12,7 @@ import ( ) var ( - testUserJSON = "test/user.json" + testUserJSON = "test/user.json" testVerboseUserJSON = "test/verbose_user.json" ) @@ -86,9 +86,9 @@ func TestVerboseUserslist(t *testing.T) { if err != nil { t.Errorf("Verbose Users.List returned error: %v", err) } - jane := UserVerboseResult{ Email: "jane.chef@user.com", FirstName: "jane", LastName: "chef_user" } - yael := UserVerboseResult{ Email: "yael.chef@user.com", FirstName: "yael", LastName: "smith" } - listWant := map[string]UserVerboseResult{ "janechef": jane, "yaelsmith": yael } + jane := UserVerboseResult{Email: "jane.chef@user.com", FirstName: "jane", LastName: "chef_user"} + yael := UserVerboseResult{Email: "yael.chef@user.com", FirstName: "yael", LastName: "smith"} + listWant := map[string]UserVerboseResult{"janechef": jane, "yaelsmith": yael} if !reflect.DeepEqual(users, listWant) { t.Errorf("Verbose Users.List returned %+v, want %+v", users, listWant) } From e5f6a31692b6c4126a88fec68e51b616da542528 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 8 Mar 2020 11:07:34 -0700 Subject: [PATCH 54/65] Clean up TLS testing --- examples/README.md | 8 ++++---- examples/chefapi_examples/Berksfile.lock | 2 -- .../files/default/go/src/chefapi_test/bin/association | 6 +++--- .../files/default/go/src/chefapi_test/bin/authenticate | 2 +- .../files/default/go/src/chefapi_test/bin/cookbook | 2 +- .../files/default/go/src/chefapi_test/bin/creds | 6 ++++-- .../files/default/go/src/chefapi_test/bin/group | 2 +- .../files/default/go/src/chefapi_test/bin/license | 2 +- .../files/default/go/src/chefapi_test/bin/node | 2 +- .../files/default/go/src/chefapi_test/bin/organization | 2 +- .../files/default/go/src/chefapi_test/bin/sandbox | 2 +- .../files/default/go/src/chefapi_test/bin/search | 2 +- .../default/go/src/chefapi_test/bin/search_pagination | 2 +- .../files/default/go/src/chefapi_test/bin/status | 2 +- .../files/default/go/src/chefapi_test/bin/universe | 2 +- .../files/default/go/src/chefapi_test/bin/user | 2 +- .../files/default/go/src/chefapi_test/testapi/testapi.go | 4 ++-- examples/chefapi_examples/metadata.rb | 1 - examples/chefapi_examples/recipes/chef_objects.rb | 5 ----- examples/chefapi_examples/recipes/chefapi.rb | 2 ++ examples/chefapi_examples/recipes/setup.rb | 9 +++++---- 21 files changed, 32 insertions(+), 35 deletions(-) diff --git a/examples/README.md b/examples/README.md index e28bd89..9afadf5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,22 +1,22 @@ # Examples for using the api ## Coded samples -The code directory has examples of using the client code to exercise the chef api end points. +The cmd directory has examples of using the client code to exercise the chef api end points. The bin directory has commands for invoking the code. The inspec directory has inspec tests to verify the output from invoking the code. -The chefapi_examples cookbook creates a chef server instance. Then runs the code and verifies +The chefapi_examples cookbook creates a chef server instance. Test kitchen can run the sample code and verify the resulting output using inspec tests. ## Cookbook and kitchen testing Run kitchen converge to create a chef server instance in a linux image running under vagrant. -Run kitchen verify to code run the go examples that using the client api and to see confirm +Run kitchen verify to run the go examples using the client api and to confirm that the results are as expected. ## Looking at the output from an api call Sometimes you might want to see what output gets created by an api call. Inspec tends to hide and mask the output. You can use kitchen login to access the linux image. Use "cd /go/src/chefapi_test/bin" to access the bin directory and run any of the commands to run the api sample use code and see -the results. Running the bin commands by adding the --tags debug option will show more detail. +the results. Running the bin commands by adding the -tags debug option will show more detail. ## Creating a client On the test image /go/src/chefapi_test/testapi/testapi.go has code that creates a client diff --git a/examples/chefapi_examples/Berksfile.lock b/examples/chefapi_examples/Berksfile.lock index 0c8838e..c250eba 100644 --- a/examples/chefapi_examples/Berksfile.lock +++ b/examples/chefapi_examples/Berksfile.lock @@ -10,8 +10,6 @@ GRAPH chefapi_examples (0.1.0) chef-server (>= 0.0.0) fileutils (>= 0.0.0) - jn_hosts (>= 0.0.0) line (>= 0.0.0) fileutils (1.3.1) - jn_hosts (0.0.4) line (2.5.0) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association index 23498b6..7cfd9a8 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/association @@ -6,6 +6,6 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/association/association_setup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false -go run ${BASE}/../cmd/association/association.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false -go run ${BASE}/../cmd/association/association_cleanup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false +go run ${BASE}/../cmd/association/association_setup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} ${SSLBYPASS} +go run ${BASE}/../cmd/association/association.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} +go run ${BASE}/../cmd/association/association_cleanup.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate index 755acfb..bd92929 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/authenticate @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/authenticate/authenticate.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false +go run ${BASE}/../cmd/authenticate/authenticate.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook index 05d8313..684d55d 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/cookbook @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/cookbook/cookbook.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false +go run ${BASE}/../cmd/cookbook/cookbook.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds index e6afce1..b663e81 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/creds @@ -1,5 +1,7 @@ CHEFUSER='pivotal' KEYFILE='/etc/opscode/pivotal.pem' -CHEFGLOBALURL='https://testhost/' +CHEFGLOBALURL='https://localhost/' # The trailing / on the url is critically important. The api does not work without it. -CHEFORGANIZATIONURL='https://testhost/organizations/test/' +CHEFORGANIZATIONURL='https://localhost/organizations/test/' +# Use the self signed certificates +SSLBYPASS='false' diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group index f83dc49..653923d 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/group @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/group/group.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false +go run ${BASE}/../cmd/group/group.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license index f8a4275..effd2ca 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/license @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/license/license.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false +go run ${BASE}/../cmd/license/license.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node index c0fac34..ccf0049 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/node @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/node/nodes.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false +go run ${BASE}/../cmd/node/nodes.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization index 9d943f7..ad9c0aa 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/organization @@ -10,4 +10,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/organization/organization.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false +go run ${BASE}/../cmd/organization/organization.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox index eeb285d..f8c5105 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/sandbox @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/sandbox/sandbox.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false +go run ${BASE}/../cmd/sandbox/sandbox.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search index 18ae712..cd57caf 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/search/search.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false +go run ${BASE}/../cmd/search/search.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination index f99012d..8b9647e 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/search_pagination @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/search/search_pagination.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false +go run ${BASE}/../cmd/search/search_pagination.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status index dddb362..7fc5ffb 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/status @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/status/status.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false +go run ${BASE}/../cmd/status/status.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe index b972b2b..a67ffae 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/universe @@ -7,4 +7,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/universe/universe.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} false +go run ${BASE}/../cmd/universe/universe.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user index 6836491..6b0135e 100755 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/user @@ -6,4 +6,4 @@ BASE=$(dirname $0) . ${BASE}/setup . ${BASE}/creds -go run ${BASE}/../cmd/user/user.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} false +go run ${BASE}/../cmd/user/user.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go index 6c4290c..82ab37e 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/testapi/testapi.go @@ -61,8 +61,8 @@ func clientKey(filepath string) string { // chefCerts creats a cert pool for the self signed certs // reference https://forfuncsake.github.io/post/2017/08/trust-extra-ca-cert-in-go-app/ func chefCerts() *x509.CertPool { - const localCertFile = "/var/opt/opscode/nginx/ca/testhost.crt" - certPool := x509.SystemCertPool() + const localCertFile = "/var/opt/opscode/nginx/ca/localhost.crt" + certPool, _ := x509.SystemCertPool() if certPool == nil { certPool = x509.NewCertPool() } diff --git a/examples/chefapi_examples/metadata.rb b/examples/chefapi_examples/metadata.rb index 607d3e5..de79d08 100644 --- a/examples/chefapi_examples/metadata.rb +++ b/examples/chefapi_examples/metadata.rb @@ -6,5 +6,4 @@ chef_version '>= 13' depends 'chef-server' depends 'fileutils' -depends 'jn_hosts' depends 'line' diff --git a/examples/chefapi_examples/recipes/chef_objects.rb b/examples/chefapi_examples/recipes/chef_objects.rb index c96bdc5..60c69c9 100644 --- a/examples/chefapi_examples/recipes/chef_objects.rb +++ b/examples/chefapi_examples/recipes/chef_objects.rb @@ -4,11 +4,6 @@ command 'hostname testhost' end -append_if_no_line 'add hostname to /etc/hosts' do - line '127.0.01 testhost' - path '/etc/hosts' -end - # Create an organization execute 'create test organization' do diff --git a/examples/chefapi_examples/recipes/chefapi.rb b/examples/chefapi_examples/recipes/chefapi.rb index a890d17..5228d52 100644 --- a/examples/chefapi_examples/recipes/chefapi.rb +++ b/examples/chefapi_examples/recipes/chefapi.rb @@ -14,7 +14,9 @@ end git '/go/src/github.com/go-chef/chef' do + revision 'tls' repository 'https://github.com/go-chef/chef.git' + action :sync end git '/go/src/github.com/cenkalti/backoff' do diff --git a/examples/chefapi_examples/recipes/setup.rb b/examples/chefapi_examples/recipes/setup.rb index 3137f33..cc5a439 100644 --- a/examples/chefapi_examples/recipes/setup.rb +++ b/examples/chefapi_examples/recipes/setup.rb @@ -1,4 +1,4 @@ -fmt.Printf("Skip SSL %+v\n", skipssl)directory '/etc/chef/accepted_licenses' do +directory '/etc/chef/accepted_licenses' do recursive true end @@ -42,7 +42,8 @@ ignore_failure true end -hosts '127.0.0.1' do - action :create - entries %w[localhost testhost] +replace_or_add 'testhost for cert compatibility' do + path '/etc/hosts' + pattern '^127.0.0.1' + line '127.0.0.1 localhost testhost' end From 7096ff8875cd5c35709c58e7304c9ccd22ac15b3 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Tue, 10 Mar 2020 21:07:57 -0700 Subject: [PATCH 55/65] Download the current branch from github when doing knife converge for testing Use a knife.rb file when uploading cookbooks --- .../files/default/go/src/chefapi_test/bin/knife.rb | 4 ++++ .../default/go/src/chefapi_test/cmd/cookbook/cookbook.go | 4 ++-- examples/chefapi_examples/kitchen.yml | 1 + examples/chefapi_examples/recipes/chefapi.rb | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/knife.rb diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/knife.rb b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/knife.rb new file mode 100644 index 0000000..bd98807 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/knife.rb @@ -0,0 +1,4 @@ +node_name "pivotal" +client_key "/etc/opscode/pivotal.pem" +chef_server_url "https://testhost/organizations/test" +ssl_verify_mode :verify_none diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go index 81773fe..43e6967 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/cookbook/cookbook.go @@ -102,13 +102,13 @@ func main() { } func addSampleCookbooks() (err error) { - cmd := exec.Command("knife", "cookbook", "upload", "sampbook", "testbook", "-s", "https://testhost/organizations/test", "-u", "pivotal", "-k", "/etc/opscode/pivotal.pem", "-o", "/fixtures/chef/cb/0.1.0") + cmd := exec.Command("knife", "cookbook", "upload", "sampbook", "testbook", "-c", "/go/src/chefapi_test/bin/knife.rb", "-o", "/fixtures/chef/cb/0.1.0") out, err := cmd.CombinedOutput() if err != nil { fmt.Println(os.Stderr, "Issue loading cookbook:", err) } fmt.Printf("Load 0.1.0 cookbook versions: %+v", string(out)) - cmd = exec.Command("knife", "cookbook", "upload", "sampbook", "testbook", "-s", "https://testhost/organizations/test", "-u", "pivotal", "-k", "/etc/opscode/pivotal.pem", "-o", "/fixtures/chef/cb/0.2.0") + cmd = exec.Command("knife", "cookbook", "upload", "sampbook", "testbook", "-c", "/go/src/chefapi_test/bin/knife.rb", "-o", "/fixtures/chef/cb/0.2.0") out, err = cmd.CombinedOutput() if err != nil { fmt.Println(os.Stderr, "Issue loading cookbook:", err) diff --git a/examples/chefapi_examples/kitchen.yml b/examples/chefapi_examples/kitchen.yml index 502da98..0ee1590 100644 --- a/examples/chefapi_examples/kitchen.yml +++ b/examples/chefapi_examples/kitchen.yml @@ -21,3 +21,4 @@ suites: attributes: chef-server: accept_license: true + repo_version: <%= `git rev-parse --abbrev-ref HEAD` %> diff --git a/examples/chefapi_examples/recipes/chefapi.rb b/examples/chefapi_examples/recipes/chefapi.rb index 5228d52..1fa478d 100644 --- a/examples/chefapi_examples/recipes/chefapi.rb +++ b/examples/chefapi_examples/recipes/chefapi.rb @@ -14,7 +14,7 @@ end git '/go/src/github.com/go-chef/chef' do - revision 'tls' + revision node['repo_version'] repository 'https://github.com/go-chef/chef.git' action :sync end From 14625e0c385a9c2a52223d7eee94380201398a69 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 23 Feb 2020 17:51:59 -0800 Subject: [PATCH 56/65] Add the User Update function and an example test Add api version 1 style requests and responses Update the comments and formatting for the user changes Add and example of creating a user without CreateKey or PublicKey Add examples of creating keys for a new user and for supplying a publickey Add the ChefKey struct, part of POST output Add tests for user update Fix the /user tests --- examples/README.md | 4 +- .../go/src/chefapi_test/cmd/user/user.go | 100 ++++++++++++++---- .../default/inspec/association_spec.rb | 4 +- .../integration/default/inspec/user_spec.rb | 24 +++-- examples/{code => cmd} | 0 go.mod | 1 + go.sum | 2 + http.go | 21 ++-- user.go | 44 +++++--- user_test.go | 49 ++++++++- 10 files changed, 190 insertions(+), 59 deletions(-) rename examples/{code => cmd} (100%) diff --git a/examples/README.md b/examples/README.md index 9afadf5..aefe63c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -20,5 +20,5 @@ the results. Running the bin commands by adding the -tags debug option will show ## Creating a client On the test image /go/src/chefapi_test/testapi/testapi.go has code that creates a client -for use by other api calls. For the purposes of testing, using the pivotal user and key -for all the tests works but seems like a really bad idea for any production use. +for use by other api calls. For the purposes of testing it uses the pivotal user and key +for all the tests. diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go index 5f62d59..1469e0e 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/user.go @@ -15,21 +15,59 @@ import ( func main() { client := testapi.Client() - var usr1 chef.User - usr1 = chef.User{ UserName: "usr1", + // Create a new private key when adding the user + usr1 := chef.User{ UserName: "usr1", Email: "user1@domain.io", FirstName: "user1", LastName: "fullname", DisplayName: "User1 Fullname", Password: "Logn12ComplexPwd#", + CreateKey: true, + } + + // Supply a public key + usr2 := chef.User{ UserName: "usr2", + Email: "user2@domain.io", + FirstName: "user2", + LastName: "lastname", + DisplayName: "User2 Lastname", + Password: "Logn12ComplexPwd#", + PublicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoYyN0AIhUh7Fw1+gQtR+ \n0/HY3625IUlVheoUeUz3WnsTrUGSSS4fHvxUiCJlNni1sQvcJ0xC9Bw3iMz7YVFO\nWz5SeKmajqKEnNywN8/NByZhhlLdBxBX/UN04/7aHZMoZxrrjXGLcyjvXN3uxyCO\nyPY989pa68LJ9jXWyyfKjCYdztSFcRuwF7tWgqnlsc8pve/UaWamNOTXQnyrQ6Dp\ndn+1jiNbEJIdxiza7DJMH/9/i/mLIDEFCLRPQ3RqW4T8QrSbkyzPO/iwaHl9U196\n06Ajv1RNnfyHnBXIM+I5mxJRyJCyDFo/MACc5AgO6M0a7sJ/sdX+WccgcHEVbPAl\n1wIDAQAB \n-----END PUBLIC KEY-----\n\n", + } + + err := deleteUser(client, "usr2") + fmt.Println("Delete usr2", err) + + // Neither PublicKey nor CreateKey specified + usr3 := chef.User{ UserName: "usr3", + Email: "user3@domain.io", + FirstName: "user3", + LastName: "lastname", + DisplayName: "User3 Lastname", + Password: "Logn12ComplexPwd#", } // Users userList := listUsers(client) fmt.Printf("List initial users %+v EndInitialList\n", userList) + userout := getUser(client, "pivotal") + fmt.Printf("Pivotal user %+v\n", userout) + userResult := createUser(client, usr1) - fmt.Println("Add usr1", userResult) + fmt.Printf("Add usr1 %+v\n", userResult) + + userResult = createUser(client, usr2) + fmt.Printf("Add usr2 %+v\n", userResult) + + err = deleteUser(client, "usr2") + fmt.Println("Delete usr2", err) + + userResult = createUser(client, usr3) + fmt.Printf("Add usr3 %+v\n", userResult) + + err = deleteUser(client, "usr3") + fmt.Println("Delete usr3", err) userList = listUsers(client, "email=user1@domain.io") fmt.Printf("Filter users %+v\n", userList) @@ -38,27 +76,48 @@ func main() { fmt.Printf("Verbose out %v\n", userVerboseOut) userResult = createUser(client, usr1) - fmt.Printf("Add user1 again %+v\n", userResult) + fmt.Printf("Add usr1 again %+v\n", userResult) - userout := getUser(client, "usr1") + userout = getUser(client, "usr1") fmt.Printf("Get usr1 %+v\n", userout) - userout = getUser(client, "pivotal") - fmt.Printf("Pivotal user %+v\n", userout) - userList = listUsers(client) fmt.Printf("List after adding %+v EndAddList\n", userList) - //userbody := chef.User{ FullName: "usr1new" } - //userresult := updateUser(client, "usr1", userbody) - //fmt.Printf("Update user1 %+v", userresult) + userbody := chef.User{ UserName: "usr1", DisplayName: "usr1", Email: "myuser@samp.com"} + userresult := updateUser(client, "usr1", userbody) + fmt.Printf("Update usr1 partial update %+v\n", userresult) userout = getUser(client, "usr1") - fmt.Println("Get usr1 after update %+v\n", userout) + fmt.Printf("Get usr1 after partial update %+v\n", userout) + + userbody = chef.User{ UserName: "usr1", DisplayName: "usr1", FirstName: "user", MiddleName: "mid", LastName: "name", Email: "myuser@samp.com" } + userresult = updateUser(client, "usr1", userbody) + fmt.Printf("Update usr1 full update %+v\n", userresult) + + userout = getUser(client, "usr1") + fmt.Printf("Get usr1 after full update %+v\n", userout) + + userbody = chef.User{ UserName: "usr1new", DisplayName: "usr1", FirstName: "user", MiddleName: "mid", LastName: "name", Email: "myuser@samp.com" } + userresult = updateUser(client, "usr1", userbody) + fmt.Printf("Update usr1 rename %+v\n", userresult) + + userout = getUser(client, "usr1new") + fmt.Printf("Get usr1 after rename %+v\n", userout) - err := deleteUser(client, "usr1") + userbody = chef.User{ UserName: "usr1new", DisplayName: "usr1", FirstName: "user", MiddleName: "mid", LastName: "name", Email: "myuser@samp.com", CreateKey: true} + userresult = updateUser(client, "usr1new", userbody) + fmt.Printf("Update usr1new create key %+v\n", userresult) + + userout = getUser(client, "usr1new") + fmt.Printf("Get usr1new after create key %+v\n", userout) + + err = deleteUser(client, "usr1") fmt.Println("Delete usr1", err) + err = deleteUser(client, "usr1new") + fmt.Println("Delete usr1new", err) + userList = listUsers(client) fmt.Printf("List after cleanup %+v EndCleanupList\n", userList) } @@ -113,12 +172,11 @@ func listUsersVerbose(client *chef.Client) map[string]chef.UserVerboseResult { return userList } - // updateUser uses the chef server api to update information for a single user -//func updateUser(client *chef.Client, username string, user chef.User) chef.User { - //user_update, err := client.Users.Update(username, user) - //if err != nil { - //fmt.Println("Issue updating user:", err) - //} - //return user_update -//} +func updateUser(client *chef.Client, username string, user chef.User) chef.UserResult { + user_update, err := client.Users.Update(username, user) + if err != nil { + fmt.Println("Issue updating user:", err) + } + return user_update +} diff --git a/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb index 6e4af99..52ff0af 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/association_spec.rb @@ -12,6 +12,6 @@ its('stdout') { should match(/^Deleted invitation [a-f0-9]+ for usrinvite \{Id:[a-f0-9]+ Orgname:test Username:usrinvite\}/) } its('stdout') { should match(/^User added: \{Username:usradd\}/) } its('stdout') { should match(/^Users list: \[\{User:\{Username:usradd\}\}\]/) } - its('stdout') { should match(/^User details: \{Username:usradd Email:usradd@domain.io DisplayName:UserAdd Fullname FirstName:usr LastName:add PublicKey:-----BEGIN PUBLIC KEY-----/) } - its('stdout') { should match(/^User deleted: \{Username:usradd Email:usradd@domain.io DisplayName:UserAdd Fullname FirstName:usr LastName:add PublicKey:-----BEGIN PUBLIC KEY-----/) } + its('stdout') { should match(/^User details: \{Username:usradd Email:usradd@domain.io DisplayName:UserAdd Fullname FirstName:usr LastName:add PublicKey:/) } + its('stdout') { should match(/^User deleted: \{Username:usradd Email:usradd@domain.io DisplayName:UserAdd Fullname FirstName:usr LastName:add PublicKey:/) } end diff --git a/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb index 25adcd1..cf9727b 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/user_spec.rb @@ -5,14 +5,26 @@ its('stderr') { should match(%r{^Issue creating user: POST https://localhost/users: 409}) } its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } its('stdout') { should match(%r{^List initial users map\[(?=.*pivotal:https://localhost/users/pivotal).*\] EndInitialList}) } - its('stdout') { should match(%r{^Add usr1 \{https://localhost/users/usr1 -----BEGIN RSA}) } + # might want a multi line match here to test for expirationdate, key uri and privatekey + its('stdout') { should match(%r{^Add usr1 \{Uri:https://localhost/users/usr1 ChefKey:\{Name:default PublicKey:-----BEGIN}) } + its('stdout') { should match(%r{^Add usr2 \{Uri:https://localhost/users/usr2 ChefKey:\{Name:default PublicKey:-----BEGIN}) } + its('stdout') { should match(%r{^Add usr3 \{Uri:https://localhost/users/usr3 ChefKey:\{Name: PublicKey: ExpirationDate: Uri: PrivateKey:\}\}}) } its('stdout') { should match(%r{^Filter users map\[usr1:https://localhost/users/usr1\]}) } its('stdout') { should match(/^Verbose out map\[(?=.*pivotal:)/) } - its('stdout') { should match(/^Get usr1 \{UserName:usr1 DisplayName:User1 Fullname Email:user1@domain.io ExternalAuthenticationUid: FirstName:user1 FullName: LastName:fullname MiddleName: Password: PublicKey:-----BEGIN/) } - its('stdout') { should match(/^Pivotal user \{UserName:pivotal DisplayName:Chef Server Superuser Email:root@localhost.localdomain ExternalAuthenticationUid: FirstName:Chef FullName: LastName:Server MiddleName: Password: PublicKey:-----BEGIN/) } + its('stdout') { should match(/^Get usr1 \{(?=.*UserName:usr1)(?=.*DisplayName:User1 Fullname)(?=.*Email:user1@domain.io)(?=.*ExternalAuthenticationUid:)(?=.*FirstName:user1)(?=.*LastName:fullname)(?=.*MiddleName:)(?=.*Password:)(?=.*PublicKey:)(?=.*RecoveryAuthenticationEnabled:false).*/) } + its('stdout') { should match(/^Pivotal user \{(?=.*UserName:pivotal)(?=.*DisplayName:Chef Server Superuser)(?=.*Email:root@localhost.localdomain)(?=.*ExternalAuthenticationUid:)(?=.*FirstName:Chef)(?=.*LastName:Server)(?=.*MiddleName:)(?=.*Password:)(?=.*PublicKey:)/) } its('stdout') { should match(%r{^List after adding map\[(?=.*pivotal:https://localhost/users/pivotal)(?=.*usr1:https://localhost/users/usr1).*\] EndAddList}) } - # its('stdout') { should match(/^Update usr1 /) } - # its('stdout') { should match(/^Get usr1 after update /) } - its('stdout') { should match(/^Delete usr1 /) } + its('stdout') { should match(/^Get usr1 \{(?=.*UserName:usr1)(?=.*DisplayName:User1 Fullname)(?=.*Email:user1@domain.io)(?=.*ExternalAuthenticationUid:)(?=.*FirstName:user1)(?=.*LastName:fullname)(?=.*MiddleName:)(?=.*Password:)(?=.*PublicKey:)/) } + its('stdout') { should match(%r{^List after adding map\[(?=.*pivotal:https://localhost/users/pivotal)(?=.*usr1:https://localhost/users/usr1).*\] EndAddList}) } + # TODO: - update and create new private key + # TODO - is admin a thing + its('stdout') { should match(%r{^Update usr1 partial update \{Uri:https://localhost/users/usr1 ChefKey:\{}) } + its('stdout') { should match(/^Get usr1 after partial update \{(UserName:usr1)(?=.*DisplayName:usr1)(?=.*Email:myuser@samp.com)(?=.*ExternalAuthenticationUid:)(?=.*FirstName:user1)(?=.*LastName:fullname)(?=.*MiddleName:)(?=.*Password:)(?=.*PublicKey:)(?=.*RecoveryAuthenticationEnabled:false).*\}/) } + its('stdout') { should match(%r{^Update usr1 full update \{Uri:https://localhost/users/usr1 ChefKey:\{Name: PublicKey: ExpirationDate: Uri: PrivateKey:\}}) } + its('stdout') { should match(/^Get usr1 after full update \{(UserName:usr1)(?=.*DisplayName:usr1)(?=.*Email:myuser@samp.com)(?=.*ExternalAuthenticationUid:)(?=.*FirstName:user)(?=.*LastName:name)(?=.*MiddleName:mid)(?=.*Password:)(?=.*PublicKey:)(?=.*RecoveryAuthenticationEnabled:false).*\}/) } + its('stdout') { should match(%r{^Update usr1 rename \{Uri:https://localhost/users/usr1new ChefKey:\{.*\}}) } + its('stdout') { should match(/^Get usr1 after rename \{(UserName:usr1new)(?=.*DisplayName:usr1)(?=.*Email:myuser@samp.com)(?=.*ExternalAuthenticationUid:)(?=.*FirstName:user)(?=.*LastName:name)(?=.*MiddleName:mid Password:)(?=.*PublicKey:)(?=.*RecoveryAuthenticationEnabled:false).*\}/) } + its('stdout') { should match(%r{^Delete usr1 DELETE https://localhost/users/usr1: 404}) } + its('stdout') { should match(/^Delete usr1new /) } its('stdout') { should match(%r{^List after cleanup map\[(?=.*pivotal:https://localhost/users/pivotal).*\] EndCleanupList}) } end diff --git a/examples/code b/examples/cmd similarity index 100% rename from examples/code rename to examples/cmd diff --git a/go.mod b/go.mod index a680559..898830a 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/google/go-cmp v0.4.0 github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de // indirect + github.com/r3labs/diff v0.0.0-20191120142937-b4ed99a31f5a github.com/smartystreets/assertions v1.0.1 // indirect github.com/smartystreets/goconvey v1.6.4 github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index c963745..826d5c9 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/r3labs/diff v0.0.0-20191120142937-b4ed99a31f5a h1:2v4Ipjxa3sh+xn6GvtgrMub2ci4ZLQMvTaYIba2lfdc= +github.com/r3labs/diff v0.0.0-20191120142937-b4ed99a31f5a/go.mod h1:ozniNEFS3j1qCwHKdvraMn1WJOsUxHd7lYfukEIS4cs= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= diff --git a/http.go b/http.go index f7bab13..639c2bb 100644 --- a/http.go +++ b/http.go @@ -20,7 +20,7 @@ import ( ) // ChefVersion that we pretend to emulate -const ChefVersion = "11.12.0" +const ChefVersion = "14.0.0" // Body wraps io.Reader and adds methods for calculating hashes and detecting content type Body struct { @@ -297,17 +297,18 @@ func (ac AuthConfig) SignRequest(request *http.Request) error { } vals := map[string]string{ - "Method": request.Method, - "Hashed Path": HashStr(endpoint), - "Accept": "application/json", - "X-Chef-Version": ChefVersion, - "X-Ops-Timestamp": time.Now().UTC().Format(time.RFC3339), - "X-Ops-UserId": ac.ClientName, - "X-Ops-Sign": "algorithm=sha1;version=1.0", - "X-Ops-Content-Hash": request.Header.Get("X-Ops-Content-Hash"), + "Method": request.Method, + "Hashed Path": HashStr(endpoint), + "Accept": "application/json", + "X-Chef-Version": ChefVersion, + "X-Ops-Server-API-Version": "1", + "X-Ops-Timestamp": time.Now().UTC().Format(time.RFC3339), + "X-Ops-UserId": ac.ClientName, + "X-Ops-Sign": "algorithm=sha1;version=1.0", + "X-Ops-Content-Hash": request.Header.Get("X-Ops-Content-Hash"), } - for _, key := range []string{"Method", "Accept", "X-Chef-Version", "X-Ops-Timestamp", "X-Ops-UserId", "X-Ops-Sign"} { + for _, key := range []string{"Method", "Accept", "X-Chef-Version", "X-Ops-Server-API-Version", "X-Ops-Timestamp", "X-Ops-UserId", "X-Ops-Sign"} { request.Header.Set(key, vals[key]) } diff --git a/user.go b/user.go index 57f6f76..bb3cb43 100644 --- a/user.go +++ b/user.go @@ -16,17 +16,25 @@ type User struct { Email string `json:"email,omitempty"` ExternalAuthenticationUid string `json:"external_authentication_uid,omitempty"` // this or password FirstName string `json:"first_name,omitempty"` - FullName string `json:"full_name,omitempty"` LastName string `json:"last_name,omitempty"` MiddleName string `json:"middle_name,omitempty"` Password string `json:"password,omitempty"` // Valid password - PublicKey string `json:"public_key,omitempty"` // not for Create + CreateKey bool `json:"create_key,omitempty"` // Cannot be passed with PublicKey + PublicKey string `json:"public_key,omitempty"` // Cannot be passed with CreateKey RecoveryAuthenticationEnabled bool `json:"recovery_authentication_enabled,omitempty"` } type UserResult struct { - Uri string `json:"uri,omitempty"` - PrivateKey string `json:"private_key,omitempty"` + Uri string `json:"uri,omitempty"` + ChefKey ChefKey `json:"chef_key,omitempty"` +} + +type ChefKey struct { + Name string `json:"name"` + PublicKey string `json:"public_key"` + ExpirationDate string `json:"expiration_date"` + Uri string `json:"uri"` + PrivateKey string `json:"private_key"` } type UserVerboseResult struct { @@ -47,9 +55,8 @@ type UserKeyResult struct { Expired string `json:"expired,omitempty"` } -// /users GET // List lists the users in the Chef server. -// +// /users GET // Chef API docs: https://docs.chef.io/api_chef_server.html#users func (e *UserService) List(filters ...string) (userlist map[string]string, err error) { url := "users" @@ -60,9 +67,8 @@ func (e *UserService) List(filters ...string) (userlist map[string]string, err e return } +// VerboseList lists the users in the Chef server in verbose format. // /users GET -// List lists the users in the Chef server in verbose format. -// // Chef API docs: https://docs.chef.io/api_chef_server.html#users func (e *UserService) VerboseList(filters ...string) (userlist map[string]UserVerboseResult, err error) { url := "users" @@ -74,8 +80,8 @@ func (e *UserService) VerboseList(filters ...string) (userlist map[string]UserVe return } +// Create Creates a User on the chef server // /users POST -// Creates a User on the chef server // 201 = sucesss // 400 - invalid (missing display_name, email,( password or external) among other things) // username must be lower case without spaces @@ -120,11 +126,23 @@ func (e *UserService) Get(name string) (user User, err error) { return } +// Update updates a user on the Chef server. +// /users/USERNAME PUT +// 200 - updated +// 401 - not authenticated +// 403 - not authorizated +// 404 - user doesn't exist +// 409 - new user name is already in use +// +// Chef API docs: https://docs.chef.io/api_chef_server.html#users-name +func (e *UserService) Update(name string, user User) (userUpdate UserResult, err error) { + url := fmt.Sprintf("users/%s", name) + body, err := JSONReader(user) + err = e.client.magicRequestDecoder("PUT", url, body, &userUpdate) + return +} + // TODO: -// API /users/USERNAME GET external_authentication_uid and email filters - filters is implemented. This may be ok. -// note that the combination of verbose and filters is not supported -// API /users/USERNAME GET verbose parameter -// API /users/USERNAME PUT issue #145 // API /users/USERNAME/keys GET issue #130 // API /users/USERNAME/keys POST issue #130 // API /users/USERNAME/keys/Key DELETE issue #130 diff --git a/user_test.go b/user_test.go index ed74ad8..409db39 100644 --- a/user_test.go +++ b/user_test.go @@ -3,6 +3,7 @@ package chef import ( "encoding/json" "fmt" + "github.com/r3labs/diff" "io" "log" "net/http" @@ -102,8 +103,14 @@ func TestUserCreate(t *testing.T) { switch { case r.Method == "POST": fmt.Fprintf(w, `{ - "uri": "https://url/for/user_name1", - "private_key": "-----BEGIN RSA PRIVATE KEY-----" + "uri": "https://chefserver/users/user_name1", + "chef_key": { + "name": "default", + "public_key": "-----BEGIN RSA PUBLIC KEY-----", + "expiration_date": "infinity", + "uri": "https://chefserver/users/user_name1/keys/default", + "private_key": "-----BEGIN RSA PRIVATE KEY-----" + } }`) } }) @@ -114,7 +121,15 @@ func TestUserCreate(t *testing.T) { if err != nil { t.Errorf("Users.Create returned error: %v", err) } - Want := UserResult{Uri: "https://url/for/user_name1", PrivateKey: "-----BEGIN RSA PRIVATE KEY-----"} + Want := UserResult{Uri: "https://chefserver/users/user_name1", + ChefKey: ChefKey{ + Name: "default", + PublicKey: "-----BEGIN RSA PUBLIC KEY-----", + ExpirationDate: "infinity", + Uri: "https://chefserver/users/user_name1/keys/default", + PrivateKey: "-----BEGIN RSA PRIVATE KEY-----", + }, + } if !reflect.DeepEqual(userresult, Want) { t.Errorf("Users.Create returned %+v, want %+v", userresult, Want) } @@ -133,7 +148,6 @@ func TestUserGet(t *testing.T) { "email": "user1@mail.com", "external_authentication_uid": "user1", "first_name": "User", - "full_name": "User S Name", "last_name": "Name", "middle_name": "S", "public_key": "-----BEGIN RSA PUBLIC KEY-----", @@ -147,7 +161,7 @@ func TestUserGet(t *testing.T) { if err != nil { t.Errorf("User.Get returned error: %v", err) } - Want := User{UserName: "user1", DisplayName: "User Name", Email: "user1@mail.com", ExternalAuthenticationUid: "user1", FirstName: "User", FullName: "User S Name", LastName: "Name", MiddleName: "S", PublicKey: "-----BEGIN RSA PUBLIC KEY-----", RecoveryAuthenticationEnabled: true} + Want := User{UserName: "user1", DisplayName: "User Name", Email: "user1@mail.com", ExternalAuthenticationUid: "user1", FirstName: "User", LastName: "Name", MiddleName: "S", PublicKey: "-----BEGIN RSA PUBLIC KEY-----", RecoveryAuthenticationEnabled: true} if !reflect.DeepEqual(user, Want) { t.Errorf("Users.Get returned %+v, want %+v", user, Want) } @@ -172,3 +186,28 @@ func TestUserDelete(t *testing.T) { t.Errorf("User.Get returned error: %v", err) } } + +func TestUserUpdate(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/user_name1", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "PUT": + fmt.Fprintf(w, `{ + "uri": "https://chefserver/users/user_name1" + }`) + } + }) + + // Update User + user := User{UserName: "user_name1", Email: "user_name1@mail.com", Password: "dummypass"} + userresult, err := client.Users.Update("user_name1", user) + if err != nil { + t.Errorf("Users.Create returned error: %v", err) + } + Want := UserResult{Uri: "https://chefserver/users/user_name1"} + if !reflect.DeepEqual(userresult, Want) { + t.Errorf("Users.Create returned %+v, want %+v", userresult, Want) + } +} From 95bce45b5cb8d3847217fc836ad1dc8a0dd0e4e2 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 7 Mar 2020 18:50:50 -0800 Subject: [PATCH 57/65] Remove testing package --- user_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/user_test.go b/user_test.go index 409db39..dfa2cf0 100644 --- a/user_test.go +++ b/user_test.go @@ -3,7 +3,6 @@ package chef import ( "encoding/json" "fmt" - "github.com/r3labs/diff" "io" "log" "net/http" From 09acf5b5c0c760d4b32f7b28f7e38e6852ba5f7c Mon Sep 17 00:00:00 2001 From: markgibbons Date: Wed, 11 Mar 2020 15:37:35 -0700 Subject: [PATCH 58/65] Add users/USERNAME/keys GET and POST processing --- .../default/go/src/chefapi_test/bin/userkeys | 9 ++ .../go/src/chefapi_test/cmd/user/userkey.go | 95 +++++++++++++++++++ .../default/inspec/userkeys_spec.rb | 7 ++ 3 files changed, 111 insertions(+) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/userkeys create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkey.go create mode 100644 examples/chefapi_examples/test/integration/default/inspec/userkeys_spec.rb diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/userkeys b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/userkeys new file mode 100755 index 0000000..88369bf --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/userkeys @@ -0,0 +1,9 @@ +#!/bin/bash + +# Group testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds +go run ${BASE}/../cmd/user/userkeys.go ${CHEFUSER} ${KEYFILE} ${CHEFGLOBALURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkey.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkey.go new file mode 100644 index 0000000..bd89a07 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkey.go @@ -0,0 +1,95 @@ +// +// Test the go-chef/chef chef server api /users/USERNAME/keys endpoints against a live chef server +// +package main + +import ( + "fmt" + "github.com/go-chef/chef" + "chefapi_test/testapi" + "os" +) + + +// main Exercise the chef server api +func main() { + client := testapi.Client() + + // Create a new private key when adding the user + usr1 := chef.User{ UserName: "usr1", + Email: "user1@domain.io", + FirstName: "user1", + LastName: "fullname", + DisplayName: "User1 Fullname", + Password: "Logn12ComplexPwd#", + CreateKey: true, + } + + // Supply a public key + usr2 := chef.User{ UserName: "usr2", + Email: "user2@domain.io", + FirstName: "user2", + LastName: "lastname", + DisplayName: "User2 Lastname", + Password: "Logn12ComplexPwd#", + PublicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoYyN0AIhUh7Fw1+gQtR+ \n0/HY3625IUlVheoUeUz3WnsTrUGSSS4fHvxUiCJlNni1sQvcJ0xC9Bw3iMz7YVFO\nWz5SeKmajqKEnNywN8/NByZhhlLdBxBX/UN04/7aHZMoZxrrjXGLcyjvXN3uxyCO\nyPY989pa68LJ9jXWyyfKjCYdztSFcRuwF7tWgqnlsc8pve/UaWamNOTXQnyrQ6Dp\ndn+1jiNbEJIdxiza7DJMH/9/i/mLIDEFCLRPQ3RqW4T8QrSbkyzPO/iwaHl9U196\n06Ajv1RNnfyHnBXIM+I5mxJRyJCyDFo/MACc5AgO6M0a7sJ/sdX+WccgcHEVbPAl\n1wIDAQAB \n-----END PUBLIC KEY-----\n\n", + } + + err := deleteUser(client, "usr2") + fmt.Println("Delete usr2", err) + + // Neither PublicKey nor CreateKey specified + usr3 := chef.User{ UserName: "usr3", + Email: "user3@domain.io", + FirstName: "user3", + LastName: "lastname", + DisplayName: "User3 Lastname", + Password: "Logn12ComplexPwd#", + } + + // User Keys + userkeys := listUserKeys(client, "usr1") + fmt.Printf("List initial user usr1 keys %+v EndInitialList\n", userkeys) + + userkeys := listUserKeys(client, "usr2") + fmt.Printf("List initial user usr2 keys %+v EndInitialList\n", userkeys) + + userkeys := listUserKeys(client, "usr3") + fmt.Printf("List initial user usr3 keys %+v EndInitialList\n", userkeys) + + // Add a key to a user + // List the user after adding + // Get key detail + // update a key + // Get key detail after update + // delete the key + // list the user keys after deleting + +} + +// listUserKeys uses the chef server api to show the keys for a user +func listUserKeys(client *chef.Client, user chef.User) (userkeys []chef.UserKeyItem) { + usrResult, err := client.Users.ListUserKeys(user) + if err != nil { + fmt.Fprintf(os.Stderr, "Issue showing keys for user %s: %+v\n", user, err) + } + return userkeys +} + +// deleteUser uses the chef server api to delete a single user +func deleteUser(client *chef.Client, name string) (err error) { + err = client.Users.Delete(name) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting org:", err) + } + return +} + +// getUserKey uses the chef server api to get information for a single user +func getUserKey(client *chef.Client, name string) chef.User { + userList, err := client.Users.Get(name) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue listing user", err) + } + return userList +} diff --git a/examples/chefapi_examples/test/integration/default/inspec/userkeys_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/userkeys_spec.rb new file mode 100644 index 0000000..93303a6 --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/userkeys_spec.rb @@ -0,0 +1,7 @@ +# Inspec tests for the user chef api go module +# + +describe command('/go/src/chefapi_test/bin/userkey') do + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } + its('stdout') { should match(%r{^List keys }) } +end From f2be94d2543d1ef374ba4817a5dc491154203b60 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Wed, 11 Mar 2020 15:39:21 -0700 Subject: [PATCH 59/65] User.go users/NAME/keys processing Create and delete the users Create Add user key processing and tests Show add key results Add user key get and delete functions:x Add the update key function and examples Update the user key examples Set up inspec tests for user keys User Keys endpoints work Add go tests for the user key functions --- .../go/src/chefapi_test/cmd/user/userkey.go | 95 ---------- .../go/src/chefapi_test/cmd/user/userkeys.go | 167 ++++++++++++++++++ .../default/inspec/userkeys_spec.rb | 15 +- user.go | 82 ++++++++- user_test.go | 166 ++++++++++++++++- 5 files changed, 418 insertions(+), 107 deletions(-) delete mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkey.go create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkeys.go diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkey.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkey.go deleted file mode 100644 index bd89a07..0000000 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkey.go +++ /dev/null @@ -1,95 +0,0 @@ -// -// Test the go-chef/chef chef server api /users/USERNAME/keys endpoints against a live chef server -// -package main - -import ( - "fmt" - "github.com/go-chef/chef" - "chefapi_test/testapi" - "os" -) - - -// main Exercise the chef server api -func main() { - client := testapi.Client() - - // Create a new private key when adding the user - usr1 := chef.User{ UserName: "usr1", - Email: "user1@domain.io", - FirstName: "user1", - LastName: "fullname", - DisplayName: "User1 Fullname", - Password: "Logn12ComplexPwd#", - CreateKey: true, - } - - // Supply a public key - usr2 := chef.User{ UserName: "usr2", - Email: "user2@domain.io", - FirstName: "user2", - LastName: "lastname", - DisplayName: "User2 Lastname", - Password: "Logn12ComplexPwd#", - PublicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoYyN0AIhUh7Fw1+gQtR+ \n0/HY3625IUlVheoUeUz3WnsTrUGSSS4fHvxUiCJlNni1sQvcJ0xC9Bw3iMz7YVFO\nWz5SeKmajqKEnNywN8/NByZhhlLdBxBX/UN04/7aHZMoZxrrjXGLcyjvXN3uxyCO\nyPY989pa68LJ9jXWyyfKjCYdztSFcRuwF7tWgqnlsc8pve/UaWamNOTXQnyrQ6Dp\ndn+1jiNbEJIdxiza7DJMH/9/i/mLIDEFCLRPQ3RqW4T8QrSbkyzPO/iwaHl9U196\n06Ajv1RNnfyHnBXIM+I5mxJRyJCyDFo/MACc5AgO6M0a7sJ/sdX+WccgcHEVbPAl\n1wIDAQAB \n-----END PUBLIC KEY-----\n\n", - } - - err := deleteUser(client, "usr2") - fmt.Println("Delete usr2", err) - - // Neither PublicKey nor CreateKey specified - usr3 := chef.User{ UserName: "usr3", - Email: "user3@domain.io", - FirstName: "user3", - LastName: "lastname", - DisplayName: "User3 Lastname", - Password: "Logn12ComplexPwd#", - } - - // User Keys - userkeys := listUserKeys(client, "usr1") - fmt.Printf("List initial user usr1 keys %+v EndInitialList\n", userkeys) - - userkeys := listUserKeys(client, "usr2") - fmt.Printf("List initial user usr2 keys %+v EndInitialList\n", userkeys) - - userkeys := listUserKeys(client, "usr3") - fmt.Printf("List initial user usr3 keys %+v EndInitialList\n", userkeys) - - // Add a key to a user - // List the user after adding - // Get key detail - // update a key - // Get key detail after update - // delete the key - // list the user keys after deleting - -} - -// listUserKeys uses the chef server api to show the keys for a user -func listUserKeys(client *chef.Client, user chef.User) (userkeys []chef.UserKeyItem) { - usrResult, err := client.Users.ListUserKeys(user) - if err != nil { - fmt.Fprintf(os.Stderr, "Issue showing keys for user %s: %+v\n", user, err) - } - return userkeys -} - -// deleteUser uses the chef server api to delete a single user -func deleteUser(client *chef.Client, name string) (err error) { - err = client.Users.Delete(name) - if err != nil { - fmt.Fprintln(os.Stderr, "Issue deleting org:", err) - } - return -} - -// getUserKey uses the chef server api to get information for a single user -func getUserKey(client *chef.Client, name string) chef.User { - userList, err := client.Users.Get(name) - if err != nil { - fmt.Fprintln(os.Stderr, "Issue listing user", err) - } - return userList -} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkeys.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkeys.go new file mode 100644 index 0000000..cdb0a2f --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/user/userkeys.go @@ -0,0 +1,167 @@ +// +// Test the go-chef/chef chef server api /users/USERNAME/keys endpoints against a live chef server +// +package main + +import ( + "fmt" + "github.com/go-chef/chef" + "chefapi_test/testapi" + "os" + "strings" +) + + +// main Exercise the chef server api +func main() { + client := testapi.Client() + + // Create a new private key when adding the user + usr1 := chef.User{ UserName: "usr1", + Email: "user1@domain.io", + FirstName: "user1", + LastName: "fullname", + DisplayName: "User1 Fullname", + Password: "Logn12ComplexPwd#", + CreateKey: true, + } + + // Supply a public key + usr2 := chef.User{ UserName: "usr2", + Email: "user2@domain.io", + FirstName: "user2", + LastName: "lastname", + DisplayName: "User2 Lastname", + Password: "Logn12ComplexPwd#", + PublicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoYyN0AIhUh7Fw1+gQtR+ \n0/HY3625IUlVheoUeUz3WnsTrUGSSS4fHvxUiCJlNni1sQvcJ0xC9Bw3iMz7YVFO\nWz5SeKmajqKEnNywN8/NByZhhlLdBxBX/UN04/7aHZMoZxrrjXGLcyjvXN3uxyCO\nyPY989pa68LJ9jXWyyfKjCYdztSFcRuwF7tWgqnlsc8pve/UaWamNOTXQnyrQ6Dp\ndn+1jiNbEJIdxiza7DJMH/9/i/mLIDEFCLRPQ3RqW4T8QrSbkyzPO/iwaHl9U196\n06Ajv1RNnfyHnBXIM+I5mxJRyJCyDFo/MACc5AgO6M0a7sJ/sdX+WccgcHEVbPAl\n1wIDAQAB \n-----END PUBLIC KEY-----\n\n", + } + + // Neither PublicKey nor CreateKey specified + usr3 := chef.User{ UserName: "usr3", + Email: "user3@domain.io", + FirstName: "user3", + LastName: "lastname", + DisplayName: "User3 Lastname", + Password: "Logn12ComplexPwd#", + } + + _ = createUser(client, usr1) + fmt.Printf("Add usr1\n") + _ = createUser(client, usr2) + fmt.Printf("Add usr2\n") + _ = createUser(client, usr3) + fmt.Printf("Add usr3\n") + + // User Keys + userkeys := listUserKeys(client, "usr1") + fmt.Printf("List initial user usr1 keys %+v\n", userkeys) + + userkeys = listUserKeys(client, "usr2") + fmt.Printf("List initial user usr2 keys %+v\n", userkeys) + + userkeys = listUserKeys(client, "usr3") + fmt.Printf("List initial user usr3 keys %+v\n", userkeys) + + // Add a key to a user + keyadd := chef.UserKey{ + KeyName: "newkey", + PublicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoYyN0AIhUh7Fw1+gQtR+ \n0/HY3625IUlVheoUeUz3WnsTrUGSSS4fHvxUiCJlNni1sQvcJ0xC9Bw3iMz7YVFO\nWz5SeKmajqKEnNywN8/NByZhhlLdBxBX/UN04/7aHZMoZxrrjXGLcyjvXN3uxyCO\nyPY989pa68LJ9jXWyyfKjCYdztSFcRuwF7tWgqnlsc8pve/UaWamNOTXQnyrQ6Dp\ndn+1jiNbEJIdxiza7DJMH/9/i/mLIDEFCLRPQ3RqW4T8QrSbkyzPO/iwaHl9U196\n06Ajv1RNnfyHnBXIM+I5mxJRyJCyDFo/MACc5AgO6M0a7sJ/sdX+WccgcHEVbPAl\n1wIDAQAB \n-----END PUBLIC KEY-----\n\n", + ExpirationDate: "infinity", + } + keyout, err := addUserKey(client, "usr1", keyadd) + fmt.Printf("Add usr1 key %+v\n", keyout) + // List the user keys after adding + userkeys = listUserKeys(client, "usr1") + fmt.Printf("List after add usr1 keys %+v\n", userkeys) + + // Add a defaultkey to user usr3 + keyadd.KeyName = "default" + keyout, err = addUserKey(client, "usr3", keyadd) + fmt.Printf("Add usr3 key %+v\n", keyout) + // List the user keys after adding + userkeys = listUserKeys(client, "usr3") + fmt.Printf("List after add usr3 keys %+v\n", userkeys) + + // Get key detail + keydetail, err := client.Users.GetUserKey("usr1", "default") + if err != nil { + fmt.Fprintf(os.Stderr, "Error displaying key detail %+v\n", err) + } + keyfold := strings.Replace(fmt.Sprintf("%+v", keydetail), "\n","",-1) + fmt.Printf("Key detail usr1 default %+v\n", keyfold) + + // update a key + keyadd.KeyName = "default" + keyupdate, err := client.Users.UpdateUserKey("usr1", "default", keyadd) + if err != nil { + fmt.Fprintf(os.Stderr, "Error updating usr1 default key%+v\n", err) + } + keyfold = strings.Replace(fmt.Sprintf("%+v", keyupdate), "\n","",-1) + fmt.Printf("Key update output usr1 default %+v\n", keyfold) + // Get key detail after update + keydetail, err = client.Users.GetUserKey("usr1", "default") + if err != nil { + fmt.Fprintf(os.Stderr, "Error displaying key detail %+v\n", err) + } + keyfold = strings.Replace(fmt.Sprintf("%+v", keydetail), "\n","",-1) + fmt.Printf("Updated key detail usr1 default %+v\n", keyfold) + + // delete the key + keydel, err := client.Users.DeleteUserKey("usr1", "default") + if err != nil { + fmt.Fprintf(os.Stderr, "Error deleting key %+v\n", err) + } + keyfold = strings.Replace(fmt.Sprintf("%+v", keydel), "\n","",-1) + fmt.Printf("List delete result usr1 keys %+v\n", keyfold) + // list the key after delete - expect 404 + keydetail, err = client.Users.GetUserKey("usr1", "default") + if err != nil { + fmt.Fprintf(os.Stderr, "Error displaying key detail %+v\n", err) + } + fmt.Printf("Deleted key detail usr1 default %+v\n", keydetail) + + // Delete the users + err = deleteUser(client, "usr1") + fmt.Printf("Delete usr1 %+v\n", err) + err = deleteUser(client, "usr2") + fmt.Printf("Delete usr2 %+v\n", err) + err = deleteUser(client, "usr3") + fmt.Printf("Delete usr3 %+v\n", err) + +} + +// listUserKeys uses the chef server api to show the keys for a user +func listUserKeys(client *chef.Client, name string) (userkeys []chef.UserKeyItem) { + userkeys, err := client.Users.ListUserKeys(name) + if err != nil { + fmt.Fprintf(os.Stderr, "Issue showing keys for user %s: %+v\n", name, err) + } + return userkeys +} + +// addUserKey uses the chef server api to add a key to user +func addUserKey(client *chef.Client, name string, keyadd chef.UserKey) (userkey chef.UserKeyItem, err error) { + userkey, err = client.Users.AddUserKey(name, keyadd) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting org:", err) + } + return +} + +// createUser uses the chef server api to create a single user +func createUser(client *chef.Client, user chef.User) chef.UserResult { + usrResult, err := client.Users.Create(user) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue creating user:", err) + } + return usrResult +} + +// deleteUser uses the chef server api to delete a single user +func deleteUser(client *chef.Client, name string) (err error) { + err = client.Users.Delete(name) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting org:", err) + } + return +} diff --git a/examples/chefapi_examples/test/integration/default/inspec/userkeys_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/userkeys_spec.rb index 93303a6..71e5dfc 100644 --- a/examples/chefapi_examples/test/integration/default/inspec/userkeys_spec.rb +++ b/examples/chefapi_examples/test/integration/default/inspec/userkeys_spec.rb @@ -1,7 +1,18 @@ # Inspec tests for the user chef api go module # -describe command('/go/src/chefapi_test/bin/userkey') do +describe command('/go/src/chefapi_test/bin/userkeys') do its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } - its('stdout') { should match(%r{^List keys }) } + its('stderr') { should match(%r{Error displaying key detail GET https://localhost/users/usr1/keys/default: 404}) } + its('stdout') { should match(%r{^List initial user usr1 keys \[\{KeyName:default Uri:https://localhost/users/usr1/keys/default Expired:false\}\]}) } + its('stdout') { should match(%r{^List initial user usr2 keys \[\{KeyName:default Uri:https://localhost/users/usr2/keys/default Expired:false\}\]}) } + its('stdout') { should match(/^List initial user usr3 keys \[\]/) } + its('stdout') { should match(%r{^Add usr1 key \{KeyName: Uri:https://localhost/users/usr1/keys/newkey Expired:false\}}) } + its('stdout') { should match(/^List after add usr1 keys \[\{(?=.*newkey)(?=.*default).*\}\]/) } + its('stdout') { should match(%r{^Add usr3 key \{KeyName: Uri:https://localhost/users/usr3/keys/default Expired:false\}}) } + its('stdout') { should match(/^List after add usr3 keys \[\{(?=.*default).*\}\]/) } + its('stdout') { should match(/^Key detail usr1 default \{KeyName:default/) } + its('stdout') { should match(/^Key update output usr1 default \{KeyName:default .*N0AIhUh7Fw1\+gQtR\+.*\}/) } + its('stdout') { should match(/^Updated key detail usr1 default \{KeyName:default .*N0AIhUh7Fw1\+gQtR\+.*\}/) } + its('stdout') { should match(/^List delete result usr1 keys \{KeyName:default .*N0AIhUh7Fw1\+gQtR\+.*\}/) } end diff --git a/user.go b/user.go index bb3cb43..abe7bfe 100644 --- a/user.go +++ b/user.go @@ -49,10 +49,10 @@ type UserKey struct { ExpirationDate string `json:"expiration_date,omitempty"` } -type UserKeyResult struct { +type UserKeyItem struct { KeyName string `json:"name,omitempty"` Uri string `json:"uri,omitempty"` - Expired string `json:"expired,omitempty"` + Expired bool `json:"expired,omitempty"` } // List lists the users in the Chef server. @@ -142,9 +142,75 @@ func (e *UserService) Update(name string, user User) (userUpdate UserResult, err return } -// TODO: -// API /users/USERNAME/keys GET issue #130 -// API /users/USERNAME/keys POST issue #130 -// API /users/USERNAME/keys/Key DELETE issue #130 -// API /users/USERNAME/keys/Key GET issue #130 -// API /users/USERNAME/keys/Key PUT issue #130 +// ListUserKeys gets all the keys for a user. +// /users/USERNAME/keys GET +// 200 - successful +// 401 - not authenticated +// 403 - not authorizated +// 404 - user doesn't exist +// +// Chef API docs: https://docs.chef.io/api_chef_server/#usersuserkeys +func (e *UserService) ListUserKeys(name string) (userkeys []UserKeyItem, err error) { + url := fmt.Sprintf("users/%s/keys", name) + err = e.client.magicRequestDecoder("GET", url, nil, &userkeys) + return +} + +// AddUserKey add a key for a user on the Chef server. +// /users/USERNAME/keys POST +// 201 - created +// 401 - not authenticated +// 403 - not authorizated +// 404 - user doesn't exist +// 409 - new name is already in use +// +// Chef API docs: https://docs.chef.io/api_chef_server.html#users-name +func (e *UserService) AddUserKey(name string, keyadd UserKey) (userkey UserKeyItem, err error) { + url := fmt.Sprintf("users/%s/keys", name) + body, err := JSONReader(keyadd) + err = e.client.magicRequestDecoder("POST", url, body, &userkey) + return +} + +// DeleteUserKey delete a key for a user. +// /users/USERNAME/keys/KEYNAME DELETE +// 200 - successful +// 401 - not authenticated +// 403 - not authorizated +// 404 - user doesn't exist +// +// Chef API docs: https://docs.chef.io/api_chef_server/#usersuserkeys +func (e *UserService) DeleteUserKey(username string, keyname string) (userkey UserKey, err error) { + url := fmt.Sprintf("users/%s/keys/%s", username, keyname) + err = e.client.magicRequestDecoder("DELETE", url, nil, &userkey) + return +} + +// GetUserKey gets a key for a user. +// /users/USERNAME/keys/KEYNAME GET +// 200 - successful +// 401 - not authenticated +// 403 - not authorizated +// 404 - user doesn't exist +// +// Chef API docs: https://docs.chef.io/api_chef_server/#usersuserkeys +func (e *UserService) GetUserKey(username string, keyname string) (userkey UserKey, err error) { + url := fmt.Sprintf("users/%s/keys/%s", username, keyname) + err = e.client.magicRequestDecoder("GET", url, nil, &userkey) + return +} + +// UpdateUserKey updates a key for a user. +// /users/USERNAME/keys/KEYNAME PUT +// 200 - successful +// 401 - not authenticated +// 403 - not authorizated +// 404 - user doesn't exist +// +// Chef API docs: https://docs.chef.io/api_chef_server/#usersuserkeys +func (e *UserService) UpdateUserKey(username string, keyname string, keyupd UserKey) (userkey UserKey, err error) { + url := fmt.Sprintf("users/%s/keys/%s", username, keyname) + body, err := JSONReader(keyupd) + err = e.client.magicRequestDecoder("PUT", url, body, &userkey) + return +} diff --git a/user_test.go b/user_test.go index dfa2cf0..04e2f8c 100644 --- a/user_test.go +++ b/user_test.go @@ -9,6 +9,7 @@ import ( "os" "reflect" "testing" + "github.com/r3labs/diff" ) var ( @@ -203,10 +204,171 @@ func TestUserUpdate(t *testing.T) { user := User{UserName: "user_name1", Email: "user_name1@mail.com", Password: "dummypass"} userresult, err := client.Users.Update("user_name1", user) if err != nil { - t.Errorf("Users.Create returned error: %v", err) + t.Errorf("Users.Update returned error: %v", err) } Want := UserResult{Uri: "https://chefserver/users/user_name1"} if !reflect.DeepEqual(userresult, Want) { - t.Errorf("Users.Create returned %+v, want %+v", userresult, Want) + t.Errorf("Users.Update returned %+v, want %+v", userresult, Want) + } +} + +func TestListUserKeys(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/user1/keys", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + fmt.Fprintf(w, `[ + { + "name": "default", + "uri": "https://chefserver/users/user1/keys/default", + "expired": false + } + ]`) + } + }) + + keyresult, err := client.Users.ListUserKeys("user1") + if err != nil { + t.Errorf("Users.ListUserKeys returned error: %v", err) + } + defaultItem := UserKeyItem{ + KeyName: "default", + Uri: "https://chefserver/users/user1/keys/default", + Expired: false, + } + Want := []UserKeyItem{defaultItem} + if !reflect.DeepEqual(keyresult, Want) { + t.Errorf("Users.ListUserKeys returned %+v, want %+v", keyresult, Want) + } +} + +func TestAddUserKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/user1/keys", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "POST": + fmt.Fprintf(w, `{ + "name": "newkey", + "uri": "https://chefserver/users/user1/keys/newkey", + "expired": false + }`) + } + }) + + keyadd := UserKey{ + KeyName: "newkey", + PublicKey: "RSA KEY", + ExpirationDate: "infinity", + } + keyresult, err := client.Users.AddUserKey("user1", keyadd) + if err != nil { + t.Errorf("Users.AddUserKey returned error: %v", err) + } + Want := UserKeyItem{ + KeyName: "newkey", + Uri: "https://chefserver/users/user1/keys/newkey", + Expired: false, + } + if !reflect.DeepEqual(keyresult, Want) { + t.Errorf("Users.AddUserKey returned %+v, want %+v", keyresult, Want) + } +} + +func TestDeleteUserKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/user1/keys/newkey", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "DELETE": + fmt.Fprintf(w, `{ + "name": "newkey", + "public_key": "RSA KEY", + "expiration_date": "infinity" + }`) + } + }) + + keyresult, err := client.Users.DeleteUserKey("user1", "newkey") + if err != nil { + t.Errorf("Users.DeleteUserKey returned error: %v", err) + } + Want := UserKey{ + KeyName: "newkey", + PublicKey: "RSA KEY", + ExpirationDate: "infinity", + } + if !reflect.DeepEqual(keyresult, Want) { + diff, _ := diff.Diff(keyresult, Want) + t.Errorf("Users.DeleteUserKey returned %+v, want %+v, differences %+v", keyresult, Want, diff) + } +} + +func TestGetUserKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/user1/keys/newkey", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + fmt.Fprintf(w, `{ + "name": "newkey", + "public_key": "RSA KEY", + "expiration_date": "infinity" + }`) + } + }) + + keyresult, err := client.Users.GetUserKey("user1", "newkey") + if err != nil { + t.Errorf("Users.GetUserKey returned error: %v", err) + } + Want := UserKey{ + KeyName: "newkey", + PublicKey: "RSA KEY", + ExpirationDate: "infinity", + } + if !reflect.DeepEqual(keyresult, Want) { + diff, _ := diff.Diff(keyresult, Want) + t.Errorf("Users.GetUserKey returned %+v, want %+v, differences %+v", keyresult, Want, diff) + } +} + +func TestUpdateUserKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/user1/keys/newkey", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "PUT": + fmt.Fprintf(w, `{ + "name": "newkey", + "public_key": "RSA NEW KEY", + "expiration_date": "infinity" + }`) + } + }) + + updkey := UserKey{ + KeyName: "newkey", + PublicKey: "RSA NEW KEY", + ExpirationDate: "infinity", + } + keyresult, err := client.Users.UpdateUserKey("user1", "newkey", updkey) + if err != nil { + t.Errorf("Users.UpdateUserKey returned error: %v", err) + } + Want := UserKey{ + KeyName: "newkey", + PublicKey: "RSA NEW KEY", + ExpirationDate: "infinity", + } + if !reflect.DeepEqual(keyresult, Want) { + diff, _ := diff.Diff(keyresult, Want) + t.Errorf("Users.UpdateUserKey returned %+v, want %+v, differences %+v", keyresult, Want, diff) } } From 7632061dcf552a125378bffc47dd190e3c1207ee Mon Sep 17 00:00:00 2001 From: markgibbons Date: Fri, 13 Mar 2020 09:04:06 -0700 Subject: [PATCH 60/65] Lint cleanup --- .../files/default/go/src/chefapi_test/cmd/node/nodes.go | 2 +- user_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go index c0c53fc..0ae9128 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/node/nodes.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "github.com/go-chef/chef" "chefapi_test/testapi" + "github.com/go-chef/chef" ) func main() { diff --git a/user_test.go b/user_test.go index 04e2f8c..e8d62fe 100644 --- a/user_test.go +++ b/user_test.go @@ -3,13 +3,13 @@ package chef import ( "encoding/json" "fmt" + "github.com/r3labs/diff" "io" "log" "net/http" "os" "reflect" "testing" - "github.com/r3labs/diff" ) var ( From 6770df4ff0e77aa35b3b59afdea54fcd6f698f83 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Fri, 13 Mar 2020 10:11:59 -0700 Subject: [PATCH 61/65] Get github.com/r3labs/diff before testing --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index c676648..4e156ec 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,6 +45,7 @@ jobs: - run: ls -Rl - run: go get - run: go get github.com/ctdk/goiardi/chefcrypto + - run: go get github.com/r3labs/diff - run: go get github.com/smartystreets/goconvey/convey - run: go get github.com/stretchr/testify/assert - run: go vet From 2e5f3b923b4f57cffcbe734483e41a83429b717a Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sat, 14 Mar 2020 15:19:07 -0700 Subject: [PATCH 62/65] Add the updated_since end point --- .../go/src/chefapi_test/bin/updated_since | 10 +++++ .../cmd/updated_since/updated_since.go | 39 +++++++++++++++++++ group.go | 4 +- http.go | 2 + updated_since.go | 25 ++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/updated_since create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/updated_since/updated_since.go create mode 100644 updated_since.go diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/updated_since b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/updated_since new file mode 100755 index 0000000..5c542e0 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/updated_since @@ -0,0 +1,10 @@ +#!/bin/bash + +# Update since testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds + +go run ${BASE}/../cmd/universe/updated_since.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/updated_since/updated_since.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/updated_since/updated_since.go new file mode 100644 index 0000000..af1c0ec --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/updated_since/updated_since.go @@ -0,0 +1,39 @@ +// +// Test the go-chef/chef chef server api /updated_since endpoints against a live server +// +package main + +import ( + "fmt" + "chefapi_test/testapi" +) + + +// main Exercise the chef server api +func main() { + // Create a client for access + client := testapi.Client() + + universe, err := client.UpdatedSince.Get(1) + if err != nil { + fmt.Println("Issue getting universe information", err) + } + fmt.Printf("List updated_since initial: %+v", universe) + + // Define a Node object + node1 := chef.NewNode("node1") + node1.RunList = []string{"pwn"} + _, err := client.Nodes.Post(node1) + if err != nil { + fmt.Fprintln(os.Stderr, "Couldn't create node node1. ", err) + } + + universe, err := client.UpdatedSince.Get(1) + if err != nil { + fmt.Println("Issue getting universe information", err) + } + fmt.Printf("List updated_since initial: %+v", universe) + + // Delete node1 ignoring errors :) + _ = client.Nodes.Delete(node1.Name) +} diff --git a/group.go b/group.go index fe0b0ff..b884a96 100644 --- a/group.go +++ b/group.go @@ -2,7 +2,7 @@ package chef import "fmt" -type GroupService struct { +type oroupService struct { client *Client } @@ -54,6 +54,8 @@ func (e *GroupService) Create(group Group) (data *GroupResult, err error) { // Update a group on the Chef server. // // Chef API docs: https://docs.chef.io/api_chef_server.html#groups +// Should this be name and group attributes? We might want to be +// able to change the name. func (e *GroupService) Update(g Group) (group Group, err error) { url := fmt.Sprintf("groups/%s", g.Name) body, err := JSONReader(g) diff --git a/http.go b/http.go index 639c2bb..0b5e76f 100644 --- a/http.go +++ b/http.go @@ -57,6 +57,7 @@ type Client struct { Search *SearchService Status *StatusService Universe *UniverseService + UpdatedSince *UpdatedSinceService Users *UserService } @@ -181,6 +182,7 @@ func NewClient(cfg *Config) (*Client, error) { c.Sandboxes = &SandboxService{client: c} c.Search = &SearchService{client: c} c.Status = &StatusService{client: c} + c.UpdatedSince = &UpdatedSinceService{client: c} c.Universe = &UniverseService{client: c} c.Users = &UserService{client: c} return c, nil diff --git a/updated_since.go b/updated_since.go new file mode 100644 index 0000000..bf441ae --- /dev/null +++ b/updated_since.go @@ -0,0 +1,25 @@ +package chef + +type UpdatedSinceService struct { + client *Client +} + +// UpdatedSince represents the body of the returned information. +type UpdatedSince { + Action string + Id string + Path string +} + +// Since gets available cookbook version information. +// +// https://docs.chef.io/api_chef_server/#updated_since +// client should have a base url with a specified organization +func (e UpdatedSinceService) Get(sequenceId string) (updated []UpdatedSince, err error) { + url := "updated_since" + if len(filters) > 0 { + url += "?seq=" + sequenceId + } + err = e.client.magicRequestDecoder("GET", url, nil, &updated) + return +} From 32fe8c80fc8e03fd6cc3a8a4e74070a8a05caeeb Mon Sep 17 00:00:00 2001 From: markgibbons Date: Sun, 15 Mar 2020 16:30:48 -0700 Subject: [PATCH 63/65] Add tests for the updated_since endpoint updated_since is long deprecated and no longer available This api wrapper always returns an error message. --- .../go/src/chefapi_test/bin/updated_since | 10 ----- .../cmd/updated_since/updated_since.go | 39 ------------------- group.go | 4 +- updated_since.go | 24 +++++++----- updated_since_test.go | 25 ++++++++++++ 5 files changed, 42 insertions(+), 60 deletions(-) delete mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/updated_since delete mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/updated_since/updated_since.go create mode 100644 updated_since_test.go diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/updated_since b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/updated_since deleted file mode 100755 index 5c542e0..0000000 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/updated_since +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# Update since testing - -BASE=$(dirname $0) - -. ${BASE}/setup -. ${BASE}/creds - -go run ${BASE}/../cmd/universe/updated_since.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/updated_since/updated_since.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/updated_since/updated_since.go deleted file mode 100644 index af1c0ec..0000000 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/updated_since/updated_since.go +++ /dev/null @@ -1,39 +0,0 @@ -// -// Test the go-chef/chef chef server api /updated_since endpoints against a live server -// -package main - -import ( - "fmt" - "chefapi_test/testapi" -) - - -// main Exercise the chef server api -func main() { - // Create a client for access - client := testapi.Client() - - universe, err := client.UpdatedSince.Get(1) - if err != nil { - fmt.Println("Issue getting universe information", err) - } - fmt.Printf("List updated_since initial: %+v", universe) - - // Define a Node object - node1 := chef.NewNode("node1") - node1.RunList = []string{"pwn"} - _, err := client.Nodes.Post(node1) - if err != nil { - fmt.Fprintln(os.Stderr, "Couldn't create node node1. ", err) - } - - universe, err := client.UpdatedSince.Get(1) - if err != nil { - fmt.Println("Issue getting universe information", err) - } - fmt.Printf("List updated_since initial: %+v", universe) - - // Delete node1 ignoring errors :) - _ = client.Nodes.Delete(node1.Name) -} diff --git a/group.go b/group.go index b884a96..2de3eb9 100644 --- a/group.go +++ b/group.go @@ -2,7 +2,7 @@ package chef import "fmt" -type oroupService struct { +type GroupService struct { client *Client } @@ -54,7 +54,7 @@ func (e *GroupService) Create(group Group) (data *GroupResult, err error) { // Update a group on the Chef server. // // Chef API docs: https://docs.chef.io/api_chef_server.html#groups -// Should this be name and group attributes? We might want to be +// Should this be name and group attributes? We might want to be // able to change the name. func (e *GroupService) Update(g Group) (group Group, err error) { url := fmt.Sprintf("groups/%s", g.Name) diff --git a/updated_since.go b/updated_since.go index bf441ae..644d224 100644 --- a/updated_since.go +++ b/updated_since.go @@ -1,25 +1,31 @@ package chef +import ( + "errors" + "strconv" +) + type UpdatedSinceService struct { client *Client } // UpdatedSince represents the body of the returned information. -type UpdatedSince { +type UpdatedSince struct { Action string - Id string - Path string + Id int64 + Path string } // Since gets available cookbook version information. // // https://docs.chef.io/api_chef_server/#updated_since -// client should have a base url with a specified organization -func (e UpdatedSinceService) Get(sequenceId string) (updated []UpdatedSince, err error) { - url := "updated_since" - if len(filters) > 0 { - url += "?seq=" + sequenceId - } +// This end point has long since been deprecated and is no longer available +// Calls will always return 404 not found errors +func (e UpdatedSinceService) Get(sequenceId int64) (updated []UpdatedSince, err error) { + url := "updated_since?seq=" + strconv.FormatInt(sequenceId, 10) err = e.client.magicRequestDecoder("GET", url, nil, &updated) + if err != nil { + err = errors.New("Update_since is a deprecated endpoint and always returns 404.") + } return } diff --git a/updated_since_test.go b/updated_since_test.go new file mode 100644 index 0000000..bf95291 --- /dev/null +++ b/updated_since_test.go @@ -0,0 +1,25 @@ +package chef + +import ( + "fmt" + "net/http" + "strings" + "testing" +) + +func TestUpdatedSinceGet(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/updated_since", func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + w.WriteHeader(404) + } + }) + + _, err := client.UpdatedSince.Get(1) + if !strings.Contains(fmt.Sprint(err), "404") { + t.Errorf("Non 404 return code: %+v", err) + } +} From 4900333ab8d25969d7e2258a15ec63139bc3d38e Mon Sep 17 00:00:00 2001 From: markgibbons Date: Tue, 17 Mar 2020 21:35:00 -0700 Subject: [PATCH 64/65] Add databag examples and tests --- databag.go | 19 ++-- .../default/go/src/chefapi_test/bin/databag | 9 ++ .../src/chefapi_test/cmd/databag/databag.go | 92 +++++++++++++++++++ http.go | 4 +- 4 files changed, 113 insertions(+), 11 deletions(-) create mode 100755 examples/chefapi_examples/files/default/go/src/chefapi_test/bin/databag create mode 100644 examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/databag/databag.go diff --git a/databag.go b/databag.go index 4ad8e3e..d2aec53 100644 --- a/databag.go +++ b/databag.go @@ -22,7 +22,7 @@ type DataBagCreateResult struct { } // DataBagListResult is the list of data bags returned by chef-api when listing -// http://docs.getchef.com/api_chef_server.html#data +// https://docs.chef.io/api_chef_server/#data type DataBagListResult map[string]string // String makes DataBagListResult implement the string result @@ -34,7 +34,7 @@ func (d DataBagListResult) String() (out string) { } // List returns a list of databags on the server -// Chef API Docs: http://docs.getchef.com/api_chef_server.html#id18 +// Chef API Docs: https://docs.chef.io/api_chef_server/#get-19 func (d *DataBagService) List() (data *DataBagListResult, err error) { path := fmt.Sprintf("data") err = d.client.magicRequestDecoder("GET", path, nil, &data) @@ -42,7 +42,7 @@ func (d *DataBagService) List() (data *DataBagListResult, err error) { } // Create adds a data bag to the server -// Chef API Docs: http://docs.getchef.com/api_chef_server.html#id19 +// Chef API Docs: https://docs.chef.io/api_chef_server/#post-7 func (d *DataBagService) Create(databag *DataBag) (result *DataBagCreateResult, err error) { body, err := JSONReader(databag) if err != nil { @@ -54,7 +54,7 @@ func (d *DataBagService) Create(databag *DataBag) (result *DataBagCreateResult, } // Delete removes a data bag from the server -// Chef API Docs: ???????????????? +// Chef API Docs: https://docs.chef.io/api_chef_server/#delete-7 func (d *DataBagService) Delete(name string) (result *DataBag, err error) { path := fmt.Sprintf("data/%s", name) err = d.client.magicRequestDecoder("DELETE", path, nil, &result) @@ -62,7 +62,7 @@ func (d *DataBagService) Delete(name string) (result *DataBag, err error) { } // ListItems gets a list of the items in a data bag. -// Chef API Docs: http://docs.getchef.com/api_chef_server.html#id20 +// Chef API Docs: https://docs.chef.io/api_chef_server/#get-20 func (d *DataBagService) ListItems(name string) (data *DataBagListResult, err error) { path := fmt.Sprintf("data/%s", name) err = d.client.magicRequestDecoder("GET", path, nil, &data) @@ -70,7 +70,7 @@ func (d *DataBagService) ListItems(name string) (data *DataBagListResult, err er } // CreateItem adds an item to a data bag -// Chef API Docs: http://docs.getchef.com/api_chef_server.html#id21 +// Chef API Docs: https://docs.chef.io/api_chef_server/#post-8 func (d *DataBagService) CreateItem(databagName string, databagItem DataBagItem) (err error) { body, err := JSONReader(databagItem) if err != nil { @@ -81,7 +81,7 @@ func (d *DataBagService) CreateItem(databagName string, databagItem DataBagItem) } // DeleteItem deletes an item from a data bag -// Chef API Docs: http://docs.getchef.com/api_chef_server.html#id22 +// Chef API Docs: https://docs.chef.io/api_chef_server/#delete-8 func (d *DataBagService) DeleteItem(databagName, databagItem string) (err error) { path := fmt.Sprintf("data/%s/%s", databagName, databagItem) err = d.client.magicRequestDecoder("DELETE", path, nil, nil) @@ -89,7 +89,7 @@ func (d *DataBagService) DeleteItem(databagName, databagItem string) (err error) } // GetItem gets an item from a data bag -// Chef API Docs: http://docs.getchef.com/api_chef_server.html#id23 +// Chef API Docs: https://docs.chef.io/api_chef_server/#get-21 func (d *DataBagService) GetItem(databagName, databagItem string) (item DataBagItem, err error) { path := fmt.Sprintf("data/%s/%s", databagName, databagItem) err = d.client.magicRequestDecoder("GET", path, nil, &item) @@ -97,7 +97,8 @@ func (d *DataBagService) GetItem(databagName, databagItem string) (item DataBagI } // UpdateItem updates an item in a data bag -// Chef API Docs: http://docs.getchef.com/api_chef_server.html#id24 +// Chef API Docs: https://docs.chef.io/api_chef_server/#put-6 +// TODO: doesn't agree with the documentation for the return func (d *DataBagService) UpdateItem(databagName, databagItemId string, databagItem DataBagItem) (err error) { body, err := JSONReader(databagItem) if err != nil { diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/databag b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/databag new file mode 100755 index 0000000..2df2cbf --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/bin/databag @@ -0,0 +1,9 @@ +#!/bin/bash + +# Data Bag testing + +BASE=$(dirname $0) + +. ${BASE}/setup +. ${BASE}/creds +go run ${BASE}/../cmd/databag/databag.go ${CHEFUSER} ${KEYFILE} ${CHEFORGANIZATIONURL} ${SSLBYPASS} diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/databag/databag.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/databag/databag.go new file mode 100644 index 0000000..9478a33 --- /dev/null +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/databag/databag.go @@ -0,0 +1,92 @@ +// +// Test the go-chef/chef chef server api /databag endpoints against a live server +// +package main + +import ( + "fmt" + "chefapi_test/testapi" + "github.com/go-chef/chef" + "os" +) + + +// main Exercise the chef server api +func main() { + client := testapi.Client() + + // List the current databags + BagList, err := client.DataBag.List() + if err != nil { + fmt.Fprintln(os.Stderr, "Issue printing the existing databags:", err) + } + fmt.Printf("List initial databags %+v\n", BagList) + + databag1 := chef.Databag{ + Name: "databag1", + } + + // Add a new databag + databagAdd, err := client.DataBags.Create(databag1) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue adding databag1:", err) + } + fmt.Println("Added databag1", databagAdd) + + // Add databag again + databagAdd, err = client.DataBags.Create(databag1) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue recreating databag1:", err) + } + fmt.Println("Recreated databag1", databagAdd) + + // Try to get a missing databag + databagOutMissing, err := client.DataBags.Get("nothere") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue getting nothere:", err) + } + fmt.Println("Get nothere", databagOutMissing) + + // List databags after adding + databagList, err = client.DataBags.List() + if err != nil { + fmt.Fprintln(os.Stderr, "Issue printing the existing databags:", err) + } + fmt.Printf("List databags after adding databag1 %+v\n", databagList) + + // Add items to a data bag + + // Update a databag item + databag1.BagName = "databag1-new" + databag1.Users = append(databag1.Users, "pivotal") + databagUpdate, err := client.DataBags.Update(databag1) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue updating databag1:", err) + } + fmt.Printf("Update databag1 %+v\n", databagUpdate) + + // list databag items + databagOut, err := client.DataBags.Get("databag1") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue getting databag1:", err) + } + fmt.Printf("Get databag1 %+v\n", databagOut) + + + // Get the contents of a data bag item + + // Delete a databag item + + // List items + + // Clean up + err = client.DataBags.Delete("databag1-new") + fmt.Println("Delete databag1", err) + + // Final list of databags + databagList, err = client.DataBags.List() + if err != nil { + fmt.Fprintln(os.Stderr, "Issue listing the final databags:", err) + } + fmt.Printf("List databags after cleanup %+v\n", databagList) +} diff --git a/http.go b/http.go index 639c2bb..ff46e88 100644 --- a/http.go +++ b/http.go @@ -193,7 +193,7 @@ func (c *Client) magicRequestDecoder(method, path string, body io.Reader, v inte return err } - debug("Request: %+v \n", req) + debug("\n\nRequest: %+v \n", req) res, err := c.Do(req, v) if res != nil { defer res.Body.Close() @@ -222,7 +222,7 @@ func (c *Client) NewRequest(method string, requestUrl string, body io.Reader) (* // parse and encode Querystring Values values := req.URL.Query() req.URL.RawQuery = values.Encode() - debug("Encoded url %+v", u) + debug("Encoded url %+v\n", u) myBody := &Body{body} From c044404f36a044265bbc84501ecaadc6656c8f40 Mon Sep 17 00:00:00 2001 From: markgibbons Date: Thu, 19 Mar 2020 16:55:49 -0700 Subject: [PATCH 65/65] Add data bag tests and examples --- databag.go | 7 +- .../src/chefapi_test/cmd/databag/databag.go | 69 ++++++++++++++----- .../default/inspec/databag_spec.rb | 19 +++++ 3 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 examples/chefapi_examples/test/integration/default/inspec/databag_spec.rb diff --git a/databag.go b/databag.go index d2aec53..f4d95c7 100644 --- a/databag.go +++ b/databag.go @@ -82,7 +82,7 @@ func (d *DataBagService) CreateItem(databagName string, databagItem DataBagItem) // DeleteItem deletes an item from a data bag // Chef API Docs: https://docs.chef.io/api_chef_server/#delete-8 -func (d *DataBagService) DeleteItem(databagName, databagItem string) (err error) { +func (d *DataBagService) DeleteItem(databagName string, databagItem string) (err error) { path := fmt.Sprintf("data/%s/%s", databagName, databagItem) err = d.client.magicRequestDecoder("DELETE", path, nil, nil) return @@ -90,7 +90,7 @@ func (d *DataBagService) DeleteItem(databagName, databagItem string) (err error) // GetItem gets an item from a data bag // Chef API Docs: https://docs.chef.io/api_chef_server/#get-21 -func (d *DataBagService) GetItem(databagName, databagItem string) (item DataBagItem, err error) { +func (d *DataBagService) GetItem(databagName string, databagItem string) (item DataBagItem, err error) { path := fmt.Sprintf("data/%s/%s", databagName, databagItem) err = d.client.magicRequestDecoder("GET", path, nil, &item) return @@ -98,8 +98,7 @@ func (d *DataBagService) GetItem(databagName, databagItem string) (item DataBagI // UpdateItem updates an item in a data bag // Chef API Docs: https://docs.chef.io/api_chef_server/#put-6 -// TODO: doesn't agree with the documentation for the return -func (d *DataBagService) UpdateItem(databagName, databagItemId string, databagItem DataBagItem) (err error) { +func (d *DataBagService) UpdateItem(databagName string, databagItemId string, databagItem DataBagItem) (err error) { body, err := JSONReader(databagItem) if err != nil { return diff --git a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/databag/databag.go b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/databag/databag.go index 9478a33..44b6d14 100644 --- a/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/databag/databag.go +++ b/examples/chefapi_examples/files/default/go/src/chefapi_test/cmd/databag/databag.go @@ -16,77 +16,108 @@ func main() { client := testapi.Client() // List the current databags - BagList, err := client.DataBag.List() + BagList, err := client.DataBags.List() if err != nil { fmt.Fprintln(os.Stderr, "Issue printing the existing databags:", err) } fmt.Printf("List initial databags %+v\n", BagList) - databag1 := chef.Databag{ + databag1 := chef.DataBag{ Name: "databag1", } // Add a new databag - databagAdd, err := client.DataBags.Create(databag1) + databagAdd, err := client.DataBags.Create(&databag1) if err != nil { fmt.Fprintln(os.Stderr, "Issue adding databag1:", err) } fmt.Println("Added databag1", databagAdd) // Add databag again - databagAdd, err = client.DataBags.Create(databag1) + databagAdd, err = client.DataBags.Create(&databag1) if err != nil { fmt.Fprintln(os.Stderr, "Issue recreating databag1:", err) } fmt.Println("Recreated databag1", databagAdd) // Try to get a missing databag - databagOutMissing, err := client.DataBags.Get("nothere") + databagOutMissing, err := client.DataBags.ListItems("nothere") if err != nil { fmt.Fprintln(os.Stderr, "Issue getting nothere:", err) } fmt.Println("Get nothere", databagOutMissing) // List databags after adding - databagList, err = client.DataBags.List() + BagList, err = client.DataBags.List() if err != nil { fmt.Fprintln(os.Stderr, "Issue printing the existing databags:", err) } - fmt.Printf("List databags after adding databag1 %+v\n", databagList) + fmt.Printf("List databags after adding databag1 %+v\n", BagList) + + + // Add item to a data bag + item1data := map[string]string{ + "id": "item1", + "type": "password", + "value": "secret", + } + item1 := chef.DataBagItem(item1data) + err = client.DataBags.CreateItem("databag1", item1) + if err != nil { + fmt.Fprintln(os.Stderr, "Issue updating databag1:", err) + } + fmt.Printf("Create databag1::item1 %+v\n", err) + - // Add items to a data bag // Update a databag item - databag1.BagName = "databag1-new" - databag1.Users = append(databag1.Users, "pivotal") - databagUpdate, err := client.DataBags.Update(databag1) + item1data["value"] = "next" + item1upd := chef.DataBagItem(item1data) + err = client.DataBags.UpdateItem("databag1", "item1", item1upd) if err != nil { fmt.Fprintln(os.Stderr, "Issue updating databag1:", err) } - fmt.Printf("Update databag1 %+v\n", databagUpdate) + fmt.Printf("Update databag1::item1 %+v\n", err) // list databag items - databagOut, err := client.DataBags.Get("databag1") + databagItems, err := client.DataBags.ListItems("databag1") if err != nil { fmt.Fprintln(os.Stderr, "Issue getting databag1:", err) } - fmt.Printf("Get databag1 %+v\n", databagOut) - + fmt.Printf("List databag1 items %+v\n", databagItems) // Get the contents of a data bag item + dataItem, err := client.DataBags.GetItem("databag1", "item1") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue getting databag1::item1 :", err) + } + fmt.Printf("Get databag1::item1 %+v\n", dataItem) // Delete a databag item + err = client.DataBags.DeleteItem("databag1", "item1") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting databag1::item1 :", err) + } + fmt.Printf("Delete databag1::item1 %+v\n", err) // List items + databagItems, err = client.DataBags.ListItems("databag1") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue getting databag1:", err) + } + fmt.Printf("List databag1 items after delete %+v\n", databagItems) // Clean up - err = client.DataBags.Delete("databag1-new") - fmt.Println("Delete databag1", err) + databag, err := client.DataBags.Delete("databag1") + if err != nil { + fmt.Fprintln(os.Stderr, "Issue deleting databag1:", err) + } + fmt.Printf("Delete databag1 %+v\n", databag) // Final list of databags - databagList, err = client.DataBags.List() + BagList, err = client.DataBags.List() if err != nil { fmt.Fprintln(os.Stderr, "Issue listing the final databags:", err) } - fmt.Printf("List databags after cleanup %+v\n", databagList) + fmt.Printf("List databags after cleanup %+v\n", BagList) } diff --git a/examples/chefapi_examples/test/integration/default/inspec/databag_spec.rb b/examples/chefapi_examples/test/integration/default/inspec/databag_spec.rb new file mode 100644 index 0000000..e9a262e --- /dev/null +++ b/examples/chefapi_examples/test/integration/default/inspec/databag_spec.rb @@ -0,0 +1,19 @@ +# Inspec tests for the databag chef api go module +# + +describe command('/go/src/chefapi_test/bin/databag') do + its('stderr') { should match(%r{^Issue recreating databag1. POST https://localhost/organizations/test/data: 409}) } + its('stderr') { should match(%r{^Issue getting nothere. GET https://localhost/organizations/test/data/nothere: 404}) } + its('stderr') { should_not match(/error|no such file|cannot find|not used|undefined/) } + its('stdout') { should match(/^List initial databags\s*$/) } + its('stdout') { should match(%r{^Added databag1 \&\{https://localhost/organizations/test/data/databag1\}}) } + its('stdout') { should match(%r{^List databags after adding databag1 databag1 => https://localhost/organizations/test/data/databag1}) } + its('stdout') { should match(/^Create databag1::item1 \/) } + its('stdout') { should match(/^Update databag1::item1 \/) } + its('stdout') { should match(%r{^List databag1 items item1 => https://localhost/organizations/test/data/databag1/item1}) } + its('stdout') { should match(/^Get databag1::item1 map\[id:item1 type:password value:next\]/) } + its('stdout') { should match(/^Delete databag1::item1 \/) } + its('stdout') { should match(/^List databag1 items after delete/) } + its('stdout') { should match(/^Delete databag1 \&\{Name:databag1 JsonClass:Chef::DataBag ChefType:data_bag/) } + its('stdout') { should match(/^List databags after cleanup\s*$/) } +end