diff --git a/cmd/download_test.go b/cmd/download_test.go index d0d8e53b..644180e1 100644 --- a/cmd/download_test.go +++ b/cmd/download_test.go @@ -42,6 +42,7 @@ func (suite *DownloadTestSuite) TestDownloadWithFileNames() { err := download(client, []string{"assets/hello.txt"}) assert.Nil(suite.T(), err) + client.Config.ReadOnly = true err = download(client, []string{"output/nope.txt"}) assert.NotNil(suite.T(), err) } diff --git a/cmd/remove.go b/cmd/remove.go index 817feeb3..e74d74c2 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -22,6 +22,12 @@ For more documentation please see http://shopify.github.io/themekit/commands/#re func remove(client kit.ThemeClient, filenames []string, wg *sync.WaitGroup) { defer wg.Done() + + if client.Config.ReadOnly { + kit.LogErrorf("[%s]environment is reaonly", kit.GreenText(client.Config.Environment)) + return + } + for _, filename := range filenames { wg.Add(1) go performRemove(client, kit.Asset{Key: filename}, wg) diff --git a/cmd/remove_test.go b/cmd/remove_test.go index 1b94cf77..6f82c63d 100644 --- a/cmd/remove_test.go +++ b/cmd/remove_test.go @@ -36,6 +36,22 @@ func (suite *RemoveTestSuite) TestRemove() { wg.Wait() } +func (suite *RemoveTestSuite) TestReadOnlyRemove() { + requested := false + client, server := newClientAndTestServer(func(w http.ResponseWriter, r *http.Request) { + requested = true + }) + defer server.Close() + + var wg sync.WaitGroup + wg.Add(1) + client.Config.ReadOnly = true + go remove(client, []string{"templates/layout.liquid"}, &wg) + wg.Wait() + + assert.Equal(suite.T(), false, requested) +} + func TestRemoveTestSuite(t *testing.T) { suite.Run(t, new(RemoveTestSuite)) } diff --git a/cmd/replace.go b/cmd/replace.go index f2c7d036..ea49dee9 100644 --- a/cmd/replace.go +++ b/cmd/replace.go @@ -28,6 +28,12 @@ For more documentation please see http://shopify.github.io/themekit/commands/#re func replace(client kit.ThemeClient, filenames []string, wg *sync.WaitGroup) { defer wg.Done() + + if client.Config.ReadOnly { + kit.LogErrorf("[%s]environment is reaonly", kit.GreenText(client.Config.Environment)) + return + } + assetsActions := map[string]assetAction{} if len(filenames) == 0 { diff --git a/cmd/replace_test.go b/cmd/replace_test.go index ca6663af..d6fcb78b 100644 --- a/cmd/replace_test.go +++ b/cmd/replace_test.go @@ -77,6 +77,22 @@ func (suite *ReplaceTestSuite) TestReplaceAll() { } } +func (suite *ReplaceTestSuite) TestReadOnlyReplace() { + requested := false + client, server := newClientAndTestServer(func(w http.ResponseWriter, r *http.Request) { + requested = true + }) + defer server.Close() + + var wg sync.WaitGroup + wg.Add(1) + client.Config.ReadOnly = true + go replace(client, []string{}, &wg) + wg.Wait() + + assert.Equal(suite.T(), false, requested) +} + func TestReplaceTestSuite(t *testing.T) { suite.Run(t, new(ReplaceTestSuite)) } diff --git a/cmd/upload.go b/cmd/upload.go index b205dc0d..7783bd25 100644 --- a/cmd/upload.go +++ b/cmd/upload.go @@ -24,6 +24,12 @@ For more documentation please see http://shopify.github.io/themekit/commands/#up func upload(client kit.ThemeClient, filenames []string, wg *sync.WaitGroup) { defer wg.Done() + + if client.Config.ReadOnly { + kit.LogErrorf("[%s]environment is reaonly", kit.GreenText(client.Config.Environment)) + return + } + var err error localAssets := []kit.Asset{} @@ -69,6 +75,10 @@ func performUpload(client kit.ThemeClient, asset kit.Asset, wg *sync.WaitGroup) } func uploadSettingsData(client kit.ThemeClient, filenames []string, wg *sync.WaitGroup) { + if client.Config.ReadOnly { + return + } + doupload := func() { asset, err := client.LocalAsset(settingsDataKey) if err != nil { diff --git a/cmd/upload_test.go b/cmd/upload_test.go index 542563eb..dd98b02f 100644 --- a/cmd/upload_test.go +++ b/cmd/upload_test.go @@ -63,6 +63,22 @@ func (suite *UploadTestSuite) TestUploadAll() { } } +func (suite *UploadTestSuite) TestReadOnlyUpload() { + requested := false + client, server := newClientAndTestServer(func(w http.ResponseWriter, r *http.Request) { + requested = true + }) + defer server.Close() + + var wg sync.WaitGroup + wg.Add(1) + client.Config.ReadOnly = true + go upload(client, []string{}, &wg) + wg.Wait() + + assert.Equal(suite.T(), false, requested) +} + func (suite *UploadTestSuite) TestUploadSettingsData() { requests := make(chan int, 100) client, server := newClientAndTestServer(func(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/watch.go b/cmd/watch.go index 76b4738e..7523956e 100644 --- a/cmd/watch.go +++ b/cmd/watch.go @@ -32,13 +32,20 @@ For more documentation please see http://shopify.github.io/themekit/commands/#wa func watch(themeClients []kit.ThemeClient) error { watchers := []*kit.FileWatcher{} defer func() { - kit.Print("Cleaning up watchers") - for _, watcher := range watchers { - watcher.StopWatching() + if len(watchers) > 0 { + kit.Print("Cleaning up watchers") + for _, watcher := range watchers { + watcher.StopWatching() + } } }() for _, client := range themeClients { + if client.Config.ReadOnly { + kit.LogErrorf("[%s]environment is reaonly", kit.GreenText(client.Config.Environment)) + continue + } + kit.Printf("[%s] Watching for file changes on host %s ", kit.GreenText(client.Config.Environment), kit.YellowText(client.Config.Domain)) watcher, err := client.NewFileWatcher(notifyFile, handleWatchEvent) if err != nil { @@ -47,8 +54,10 @@ func watch(themeClients []kit.ThemeClient) error { watchers = append(watchers, watcher) } - signal.Notify(signalChan, os.Interrupt) - <-signalChan + if len(watchers) > 0 { + signal.Notify(signalChan, os.Interrupt) + <-signalChan + } return nil } diff --git a/cmd/watch_test.go b/cmd/watch_test.go index 1723f932..26c72092 100644 --- a/cmd/watch_test.go +++ b/cmd/watch_test.go @@ -26,6 +26,17 @@ func (suite *WatchTestSuite) TestWatch() { watch([]kit.ThemeClient{client}) } +func (suite *WatchTestSuite) TestReadOnlyWatch() { + requested := false + client, server := newClientAndTestServer(func(w http.ResponseWriter, r *http.Request) { + requested = true + }) + defer server.Close() + client.Config.ReadOnly = true + watch([]kit.ThemeClient{client}) + assert.Equal(suite.T(), false, requested) +} + func (suite *WatchTestSuite) TestHandleWatchEvent() { requests := make(chan int, 1000) client, server := newClientAndTestServer(func(w http.ResponseWriter, r *http.Request) { diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 14cc4419..ad9ace79 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -12,14 +12,15 @@ There are general values that you will be able to config for all of Theme Kit ac | Attribute | Description |:-------------|:--------------------- -| password | Your API password -| theme_id | The theme that you want the command to take effect on. If you want to make changes to the current live theme you may set this value to `'live'` -| store | Your store's Shopify domain with the `.myshopify.com` postfix. -| directory | The project root directory. This allows you to run the command from another directory. +| password | Your API password +| theme_id | The theme that you want the command to take effect on. If you want to make changes to the current live theme you may set this value to `'live'` +| store | Your store's Shopify domain with the `.myshopify.com` postfix. +| directory | The project root directory. This allows you to run the command from another directory. | ignore_files | A list of patterns to ignore when executing commands. Please see the [Ignore Patterns]({{ '/ignores' | prepend: site.baseurl }}) documentation. -| ignores | A list of file paths to files that contain ignore patterns. Please see the [Ignore Patterns]({{ '/ignores' | prepend: site.baseurl }}) documentation. -| proxy | A full URL to proxy your requests through. -| timeout | Request timeout. If you have larger files in your project that may take longer than the default 30s to upload, you may want to increase this value. You can set this value to 60s for seconds or 1m for one minute. +| ignores | A list of file paths to files that contain ignore patterns. Please see the [Ignore Patterns]({{ '/ignores' | prepend: site.baseurl }}) documentation. +| proxy | A full URL to proxy your requests through. +| timeout | Request timeout. If you have larger files in your project that may take longer than the default 30s to upload, you may want to increase this value. You can set this value to 60s for seconds or 1m for one minute. +| readonly | All actions are readonly. This means you can download from this environment but you cannot do any modifications to the theme on shopify. ## Config File @@ -41,6 +42,7 @@ production: theme_id: "456" store: can-i-buy-a-feeling.myshopify.com timeout: 60s + readonly: true test: password: 16ef663594568325d64408ebcdeef528 theme_id: "789" @@ -57,14 +59,14 @@ All of the Theme Kit environment variables are prefixed with `THEMEKIT_` | Attribute | Environment Variable | |:-------------|:---------------------|:------------------| -| password | THEMEKIT_PASSWORD | | -| theme_id | THEMEKIT_THEME_ID | | -| store | THEMEKIT_STORE | | -| directory | THEMEKIT_DIRECTORY | | +| password | THEMEKIT_PASSWORD | | +| theme_id | THEMEKIT_THEME_ID | | +| store | THEMEKIT_STORE | | +| directory | THEMEKIT_DIRECTORY | | | ignore_files | THEMEKIT_IGNORE_FILES| Use a ':' as a pattern separator. | -| ignores | THEMEKIT_IGNORES | Use a ':' as a file path separator. | -| proxy | THEMEKIT_PROXY | | -| timeout | THEMEKIT_TIMEOUT | | +| ignores | THEMEKIT_IGNORES | Use a ':' as a file path separator. | +| proxy | THEMEKIT_PROXY | | +| timeout | THEMEKIT_TIMEOUT | | **Note** Any environment variable will take precedence over your `config.yml` values so please keep that in mind while debugging your config. @@ -76,14 +78,14 @@ debugging settings or scripting calls to Theme Kit from something like `cron`. | Attribute | Flag | Shortcut |:-------------|:----------------|:--------| -| password | `--password` | | -| theme_id | `--themeid` | | -| store | `--store` | -s | -| directory | `--directory` | -d | +| password | `--password` | | +| theme_id | `--themeid` | | +| store | `--store` | -s | +| directory | `--directory` | -d | | ignore_files | `--ignored-file`| | -| ignores | `--ignores` | | -| proxy | `--proxy` | | -| timeout | `--timeout` | | +| ignores | `--ignores` | | +| proxy | `--proxy` | | +| timeout | `--timeout` | | **Note** Any flag will take precedence over your `config.yml` and environment values so please keep that in mind while debugging your config. diff --git a/kit/configuration.go b/kit/configuration.go index 5b84ee29..fe538a21 100644 --- a/kit/configuration.go +++ b/kit/configuration.go @@ -23,6 +23,7 @@ type Configuration struct { Proxy string `yaml:"proxy,omitempty" json:"proxy,omitempty" env:"THEMEKIT_PROXY"` Ignores []string `yaml:"ignores,omitempty" json:"ignores,omitempty" env:"THEMEKIT_IGNORES" envSeparator:":"` Timeout time.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty" env:"THEMEKIT_TIMEOUT"` + ReadOnly bool `yaml:"readonly,omitempty" json:"readonly,omitempty" env:"-"` } // DefaultTimeout is the default timeout to kill any stalled processes. @@ -129,6 +130,7 @@ IgnoredFiles %v Proxy %v Ignores %v Timeout %v +ReadOnly %v `, conf.Password, conf.ThemeID, @@ -137,7 +139,9 @@ Timeout %v conf.IgnoredFiles, conf.Proxy, conf.Ignores, - conf.Timeout) + conf.Timeout, + conf.ReadOnly, + ) } func (conf Configuration) asYAML() *Configuration { diff --git a/kit/http_client.go b/kit/http_client.go index 29a564b1..f8bf1a06 100644 --- a/kit/http_client.go +++ b/kit/http_client.go @@ -137,11 +137,17 @@ func (client *httpClient) sendJSON(rtype requestType, event EventType, urlStr st } func (client *httpClient) sendRequest(rtype requestType, event EventType, urlStr string, body io.Reader) (*ShopifyResponse, Error) { + if client.config.ReadOnly && event != Retrieve { + return newShopifyResponse(rtype, event, urlStr, nil, fmt.Errorf("Theme is read only")) + } + req, err := client.newRequest(event, urlStr, body) if err != nil { return newShopifyResponse(rtype, event, urlStr, nil, err) } + apiLimit.Wait() + resp, respErr := client.client.Do(req) return newShopifyResponse(rtype, event, urlStr, resp, respErr) }