You've already forked watchtower
							
							
				mirror of
				https://github.com/containrrr/watchtower.git
				synced 2025-10-31 00:17:44 +02:00 
			
		
		
		
	Monitor-only for individual containers (#652)
* Add monitor-only label * Add tests for monitor-only * Treat missing monitor-only label as if the option was set to false * Add docs for container-based monitor-only * Add function doc * Fix monitor-only logic
This commit is contained in:
		| @@ -162,7 +162,7 @@ Environment Variable: WATCHTOWER_LABEL_ENABLE | ||||
| **Do not** update containers that have `com.centurylinklabs.watchtower.enable` label set to false and no `--label-enable` argument is passed. Note that only one or the other (targeting by enable label) can be used at the same time to target containers. | ||||
|  | ||||
| ## Without updating containers | ||||
| Will only monitor for new images, not update the containers. | ||||
| Will only monitor for new images, send notifications and invoke the [pre-check/post-check hooks](https://containrrr.dev/watchtower/lifecycle-hooks/), but will **not** update the containers. | ||||
|  | ||||
| > ### ⚠️ Please note | ||||
| > | ||||
| @@ -175,6 +175,8 @@ Environment Variable: WATCHTOWER_MONITOR_ONLY | ||||
|              Default: false | ||||
| ``` | ||||
|  | ||||
| Note that monitor-only can also be specified on a per-container basis with the `com.centurylinklabs.watchtower.monitor-only` label set on those containers. | ||||
|  | ||||
| ## Without restarting containers | ||||
| Do not restart containers after updating. This option can be useful when the start of the containers | ||||
| is managed by an external system such as systemd. | ||||
|   | ||||
| @@ -1,5 +1,12 @@ | ||||
| By default, watchtower will watch all containers. However, sometimes only some containers should be updated. | ||||
|  | ||||
| There are two options: | ||||
|  | ||||
| - **Fully exclude**: You can choose to exclude containers entirely from being watched by watchtower. | ||||
| - **Monitor only**: In this mode, watchtower checks for container updates, sends notifications and invokes the [pre-check/post-check hooks](https://containrrr.dev/watchtower/lifecycle-hooks/) on the containers but does **not** perform the update. | ||||
|  | ||||
| ## Full Exclude  | ||||
|  | ||||
| If you need to exclude some containers, set the _com.centurylinklabs.watchtower.enable_ label to `false`. | ||||
|  | ||||
| ```docker | ||||
| @@ -28,4 +35,24 @@ If you wish to create a monitoring scope, you will need to [run multiple instanc | ||||
|  | ||||
| Watchtower filters running containers by testing them against each configured criteria. A container is monitored if all criteria are met. For example: | ||||
| - If a container's name is on the monitoring name list (not empty `--name` argument) but it is not enabled (_centurylinklabs.watchtower.enable=false_), it won't be monitored; | ||||
| - If a container's name is not on the monitoring name list (not empty `--name` argument), even if it is enabled (_centurylinklabs.watchtower.enable=true_ and `--label-enable` flag is set), it won't be monitored; | ||||
| - If a container's name is not on the monitoring name list (not empty `--name` argument), even if it is enabled (_centurylinklabs.watchtower.enable=true_ and `--label-enable` flag is set), it won't be monitored; | ||||
|  | ||||
| ## Monitor Only | ||||
|  | ||||
| Individual containers can be marked to only be monitored (without being updated). | ||||
|  | ||||
| To do so, set the *com.centurylinklabs.watchtower.monitor-only* label to `true` on that container. | ||||
|  | ||||
| ```docker | ||||
| LABEL com.centurylinklabs.watchtower.monitor-only="true" | ||||
| ``` | ||||
|  | ||||
| Or, it can be specified as part of the `docker run` command line: | ||||
|  | ||||
| ```bash | ||||
| docker run -d --label=com.centurylinklabs.watchtower.monitor-only=true someimage | ||||
| ``` | ||||
|  | ||||
| When the label is specified on a container, watchtower treats that container exactly as if [`WATCHTOWER_MONITOR_ONLY`](https://containrrr.dev/watchtower/arguments/#without_updating_containers) was set, but the effect is limited to the individual container.  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -27,3 +27,22 @@ func CreateMockContainer(id string, name string, image string, created time.Time | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| // CreateMockContainerWithConfig creates a container substitute valid for testing | ||||
| func CreateMockContainerWithConfig(id string, name string, image string, created time.Time, config *container2.Config) container.Container { | ||||
| 	content := types.ContainerJSON{ | ||||
| 		ContainerJSONBase: &types.ContainerJSONBase{ | ||||
| 			ID:      id, | ||||
| 			Image:   image, | ||||
| 			Name:    name, | ||||
| 			Created: created.String(), | ||||
| 		}, | ||||
| 		Config: config, | ||||
| 	} | ||||
| 	return *container.NewContainer( | ||||
| 		&content, | ||||
| 		&types.ImageInspect{ | ||||
| 			ID: image, | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,7 @@ func Update(client container.Client, params types.UpdateParams) error { | ||||
|  | ||||
| 	for i, targetContainer := range containers { | ||||
| 		stale, err := client.IsContainerStale(targetContainer) | ||||
| 		if stale && !params.NoRestart && !params.MonitorOnly && !targetContainer.HasImageInfo() { | ||||
| 		if stale && !params.NoRestart && !params.MonitorOnly && !targetContainer.IsMonitorOnly() && !targetContainer.HasImageInfo() { | ||||
| 			err = errors.New("no available image info") | ||||
| 		} | ||||
| 		if err != nil { | ||||
| @@ -45,18 +45,20 @@ func Update(client container.Client, params types.UpdateParams) error { | ||||
|  | ||||
| 	checkDependencies(containers) | ||||
|  | ||||
| 	if params.MonitorOnly { | ||||
| 		if params.LifecycleHooks { | ||||
| 			lifecycle.ExecutePostChecks(client, params) | ||||
| 	containersToUpdate := []container.Container{} | ||||
| 	if !params.MonitorOnly { | ||||
| 		for i := len(containers) - 1; i >= 0; i-- { | ||||
| 			if !containers[i].IsMonitorOnly() { | ||||
| 				containersToUpdate = append(containersToUpdate, containers[i]) | ||||
| 			} | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if params.RollingRestart { | ||||
| 		performRollingRestart(containers, client, params) | ||||
| 		performRollingRestart(containersToUpdate, client, params) | ||||
| 	} else { | ||||
| 		stopContainersInReversedOrder(containers, client, params) | ||||
| 		restartContainersInSortedOrder(containers, client, params) | ||||
| 		stopContainersInReversedOrder(containersToUpdate, client, params) | ||||
| 		restartContainersInSortedOrder(containersToUpdate, client, params) | ||||
| 	} | ||||
| 	if params.LifecycleHooks { | ||||
| 		lifecycle.ExecutePostChecks(client, params) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
| 	"github.com/containrrr/watchtower/pkg/container" | ||||
| 	"github.com/containrrr/watchtower/pkg/container/mocks" | ||||
| 	"github.com/containrrr/watchtower/pkg/types" | ||||
| 	container2 "github.com/docker/docker/api/types/container" | ||||
| 	cli "github.com/docker/docker/client" | ||||
| 	"time" | ||||
|  | ||||
| @@ -80,4 +81,73 @@ var _ = Describe("the update action", func() { | ||||
| 			}) | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	When("watchtower has been instructed to monitor only", func() { | ||||
| 		When("certain containers are set to monitor only", func() { | ||||
| 			BeforeEach(func() { | ||||
| 				client = CreateMockClient( | ||||
| 					&TestData{ | ||||
| 						NameOfContainerToKeep: "test-container-02", | ||||
| 						Containers: []container.Container{ | ||||
| 							CreateMockContainer( | ||||
| 								"test-container-01", | ||||
| 								"test-container-01", | ||||
| 								"fake-image1:latest", | ||||
| 								time.Now()), | ||||
| 							CreateMockContainerWithConfig( | ||||
| 								"test-container-02", | ||||
| 								"test-container-02", | ||||
| 								"fake-image2:latest", | ||||
| 								time.Now(), | ||||
| 								&container2.Config{ | ||||
| 									Labels: map[string]string{ | ||||
| 										"com.centurylinklabs.watchtower.monitor-only": "true", | ||||
| 									}, | ||||
| 								}), | ||||
| 						}, | ||||
| 					}, | ||||
| 					dockerClient, | ||||
| 					false, | ||||
| 					false, | ||||
| 				) | ||||
| 			}) | ||||
|  | ||||
| 			It("should not update those containers", func() { | ||||
| 				err := actions.Update(client, types.UpdateParams{Cleanup: true}) | ||||
| 				Expect(err).NotTo(HaveOccurred()) | ||||
| 				Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1)) | ||||
| 			}) | ||||
| 		}) | ||||
|  | ||||
| 		When("monitor only is set globally", func() { | ||||
| 			BeforeEach(func() { | ||||
| 				client = CreateMockClient( | ||||
| 					&TestData{ | ||||
| 						Containers: []container.Container{ | ||||
| 							CreateMockContainer( | ||||
| 								"test-container-01", | ||||
| 								"test-container-01", | ||||
| 								"fake-image:latest", | ||||
| 								time.Now()), | ||||
| 							CreateMockContainer( | ||||
| 								"test-container-02", | ||||
| 								"test-container-02", | ||||
| 								"fake-image:latest", | ||||
| 								time.Now()), | ||||
| 						}, | ||||
| 					}, | ||||
| 					dockerClient, | ||||
| 					false, | ||||
| 					false, | ||||
| 				) | ||||
| 			}) | ||||
|  | ||||
| 			It("should not update any containers", func() { | ||||
| 				err := actions.Update(client, types.UpdateParams{MonitorOnly: true}) | ||||
| 				Expect(err).NotTo(HaveOccurred()) | ||||
| 				Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0)) | ||||
| 			}) | ||||
| 		}) | ||||
|  | ||||
| 	}) | ||||
| }) | ||||
|   | ||||
| @@ -90,6 +90,22 @@ func (c Container) Enabled() (bool, bool) { | ||||
| 	return parsedBool, true | ||||
| } | ||||
|  | ||||
| // IsMonitorOnly returns the value of the monitor-only label. If the label | ||||
| // is not set then false is returned. | ||||
| func (c Container) IsMonitorOnly() bool { | ||||
| 	rawBool, ok := c.getLabelValue(monitorOnlyLabel) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	parsedBool, err := strconv.ParseBool(rawBool) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return parsedBool | ||||
| } | ||||
|  | ||||
| // Scope returns the value of the scope UID label and if the label | ||||
| // was set. | ||||
| func (c Container) Scope() (string, bool) { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ const ( | ||||
| 	watchtowerLabel       = "com.centurylinklabs.watchtower" | ||||
| 	signalLabel           = "com.centurylinklabs.watchtower.stop-signal" | ||||
| 	enableLabel           = "com.centurylinklabs.watchtower.enable" | ||||
| 	monitorOnlyLabel      = "com.centurylinklabs.watchtower.monitor-only" | ||||
| 	dependsOnLabel        = "com.centurylinklabs.watchtower.depends-on" | ||||
| 	zodiacLabel           = "com.centurylinklabs.zodiac.original-image" | ||||
| 	scope                 = "com.centurylinklabs.watchtower.scope" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user