Skip to content

cli: extract client bindings of the v1 API#1278

Merged
stuartnelson3 merged 4 commits intoprometheus:masterfrom
simonpasquier:add-client-v1
Mar 28, 2018
Merged

cli: extract client bindings of the v1 API#1278
stuartnelson3 merged 4 commits intoprometheus:masterfrom
simonpasquier:add-client-v1

Conversation

@simonpasquier
Copy link
Member

@simonpasquier simonpasquier commented Mar 8, 2018

This is the first pass for #1035. It is mostly based on prometheus/client_golang#333 with a few changes. Notably it uses types.Silence instead of model.Silence because model.Silence represents silence ID as int instead of string.

For now the client code lives in the github.com/prometheus/alertmanager/api package because it was easier this way and I didn't know where it should live otherwise. The code lives in the github.com/prometheus/alertmanager/cli package. A subsequent PR should move all the CLI commands to github.com/prometheus/alertmanager/cli/cmd and make them use this new code.

cc @stuartnelson3

@beorn7
Copy link
Member

beorn7 commented Mar 8, 2018

I have a vague memory of a conversation with @stuartnelson3 , concluding that the AM API client should live in github.com/prometheus/client_golang/api/alertmanager . I don't feel strongly about it. But if you put a Go client directly into the alertmanager repo, will you then also put a Java or C++ or Python client there?

@simonpasquier
Copy link
Member Author

I understood from this comment that the agreement was to put the client code here. But re-reading #1035, I realized that the scope was only to extract the interactions with the API from the cli package. I'll rework the PR...

@beorn7
Copy link
Member

beorn7 commented Mar 8, 2018

Got it, so I misremembered the conclusion. All great then. Go on… :o)

@simonpasquier simonpasquier changed the title api: add client bindings for the v1 API [WIP] cli: extract client bindings for the v1 API Mar 8, 2018
@simonpasquier simonpasquier changed the title [WIP] cli: extract client bindings for the v1 API [WIP] cli: extract client bindings of the v1 API Mar 8, 2018
@stuartnelson3
Copy link
Contributor

@simonpasquier would you like me to take a look now, or wait until you remove [WIP]?

@simonpasquier
Copy link
Member Author

@stuartnelson3 wait until I remove the WIP please.

@simonpasquier simonpasquier force-pushed the add-client-v1 branch 2 times, most recently from cafa171 to 8e1bdc6 Compare March 12, 2018 09:41
This is a continuation of [1] but the code is kept in the alertmanage
repository rather than having it in client_golang.

[1] prometheus/client_golang#333

Co-Authored-By: Fabian Reinartz <fab.reinartz@gmail.com>
Co-Authored-By: Tristan Colgate <tcolgate@gmail.com>
Co-Authored-By: Corin Lawson <corin@responsight.com>
Co-Authored-By: stuart nelson <stuartnelson3@gmail.com>
@simonpasquier simonpasquier changed the title [WIP] cli: extract client bindings of the v1 API cli: extract client bindings of the v1 API Mar 13, 2018
@stuartnelson3 stuartnelson3 self-requested a review March 15, 2018 12:46
Copy link
Contributor

@stuartnelson3 stuartnelson3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the comments inline, the List methods for both silences and alerts contact endpoints that support filtering. The cli uses this filtering. It would be good to expose this functionality so that cli and other consumers can filter their responses.

cli/client.go Outdated
}
for _, v := range sil.Matchers {
s.Matchers = append(s.Matchers, &types.Matcher{Name: string(v.Name), Value: v.Value, IsRegex: v.IsRegex})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these lines necessary? It looks like a types.Silences is just being copied into a new one. I'm able to remove this extra code and have the method work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is left-over from a previous version.

cli/client.go Outdated
func (h *httpSilenceAPI) Set(ctx context.Context, sil types.Silence) (string, error) {
var (
u *url.URL
method string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed and http.MethodPost inlined.

cli/client.go Outdated
// List returns all the active alerts.
List(ctx context.Context) ([]*model.Alert, error)
// Push sends a list of alerts to the Alertmanager.
Push(ctx context.Context, alerts ...model.Alert) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having import issues when trying to use this library, since there's an import mismatch:

./main.go:50:15: cannot use alert (type "github.com/prometheus/common/model".Alert) as type "github.com/prometheus/alertmanager/vendor/github.com/prometheus/common/model".Alert in argument to alertAPI.Push

We're running into issue because alertmanager is being used as a library, which shouldn't vendor this, when it was originally an application, which should vendor. We might have to introduce an intermediate type for the API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still having issues with this. The easiest thing to do is to introduce the intermediate type, and then in a separate PR we can decide if we want to replace references to model.Alert throughout the code base.

@brian-brazil what's the current state on using common/model? Are we deprecating it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had success when copying over the types from common/model:

diff --git a/cli/client.go b/cli/client.go
index c623b25..9d7e0d4 100644
--- a/cli/client.go
+++ b/cli/client.go
@@ -25,7 +25,6 @@ import (
 
 	"github.com/prometheus/alertmanager/config"
 	"github.com/prometheus/alertmanager/types"
-	"github.com/prometheus/common/model"
 )
 
 const (
@@ -154,9 +153,9 @@ func (h *httpStatusAPI) Get(ctx context.Context) (*ServerStatus, error) {
 // AlertAPI provides bindings for the Alertmanager's alert API.
 type AlertAPI interface {
 	// List returns all the active alerts.
-	List(ctx context.Context) ([]*model.Alert, error)
+	List(ctx context.Context) ([]*Alert, error)
 	// Push sends a list of alerts to the Alertmanager.
-	Push(ctx context.Context, alerts ...model.Alert) error
+	Push(ctx context.Context, alerts ...Alert) error
 }
 
 // NewAlertAPI returns a new AlertAPI for the client.
@@ -168,7 +167,20 @@ type httpAlertAPI struct {
 	client api.Client
 }
 
-func (h *httpAlertAPI) List(ctx context.Context) ([]*model.Alert, error) {
+type Alert struct {
+	Labels       LabelSet  `json:"labels"`
+	Annotations  LabelSet  `json:"annotations"`
+	StartsAt     time.Time `json:"startsAt,omitempty"`
+	EndsAt       time.Time `json:"endsAt,omitempty"`
+	GeneratorURL string    `json:"generatorURL"`
+}
+
+type LabelSet map[LabelName]LabelValue
+type LabelName string
+type LabelValue string
+
+// List implements the AlertAPI interface.
+func (h *httpAlertAPI) List(ctx context.Context) ([]*Alert, error) {
 	u := h.client.URL(epAlerts, nil)
 
 	req, _ := http.NewRequest(http.MethodGet, u.String(), nil)
@@ -178,13 +190,14 @@ func (h *httpAlertAPI) List(ctx context.Context) ([]*model.Alert, error) {
 		return nil, err
 	}
 
-	var alts []*model.Alert
-	err = json.Unmarshal(body, &alts)
+	var alerts []*Alert
+	err = json.Unmarshal(body, &alerts)
 
-	return alts, err
+	return alerts, err
 }
 
-func (h *httpAlertAPI) Push(ctx context.Context, alerts ...model.Alert) error {
+// Push implements the AlertAPI interface.
+func (h *httpAlertAPI) Push(ctx context.Context, alerts ...Alert) error {
 	u := h.client.URL(epAlerts, nil)
 
 	var buf bytes.Buffer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brian-brazil what's the current state on using common/model? Are we deprecating it?

The general direction is to replace it, but there's still going to be a common library one way or another.

// See the License for the specific language governing permissions and
// limitations under the License.

package cli
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be in cli, as it's an api that the cli will consume. It should probably be in the api package.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this initial change, I was following the description from #1035 where @josedonizetti said:

I propose we extract the query/parsing of the API from the amtool, and expose it properly in the cli package, moving the amtool commands to a new package inside the cli (e.g.: cli/cmd).

I'm fine with your suggestion too. The downside with this approach is that anyone wanting to use only the API client bits will have to pull the server-side dependencies too.

return err
}

func (h *httpSilenceAPI) Set(ctx context.Context, sil types.Silence) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was failing with an EOF, started working after these changes:

diff --git a/cli/client.go b/cli/client.go
index 617eac8..25ca9bd 100644
--- a/cli/client.go
+++ b/cli/client.go
@@ -19,7 +19,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
-	"net/url"
 	"time"
 
 	"github.com/prometheus/client_golang/api"
@@ -250,36 +249,30 @@ func (h *httpSilenceAPI) Delete(ctx context.Context, id string) error {
 }
 
 func (h *httpSilenceAPI) Set(ctx context.Context, sil types.Silence) (string, error) {
-	var (
-		u      *url.URL
-		method string
-	)
-
-	var buf bytes.Buffer
-	s := types.Silence{
-		Matchers:  make([]*types.Matcher, len(sil.Matchers)),
-		StartsAt:  sil.StartsAt,
-		EndsAt:    sil.EndsAt,
-		CreatedBy: sil.CreatedBy,
-		Comment:   sil.Comment,
-	}
-	for _, v := range sil.Matchers {
-		s.Matchers = append(s.Matchers, &types.Matcher{Name: string(v.Name), Value: v.Value, IsRegex: v.IsRegex})
-	}
-	if err := json.NewEncoder(&buf).Encode(&s); err != nil {
+	buf := new(bytes.Buffer)
+	if err := json.NewEncoder(buf).Encode(&sil); err != nil {
 		return "", err
 	}
 
-	u = h.client.URL(epSilences, nil)
-	method = http.MethodPost
+	u := h.client.URL(epSilences, nil)
 
-	req, _ := http.NewRequest(method, u.String(), &buf)
+	req, err := http.NewRequest(http.MethodPost, u.String(), buf)
+	if err != nil {
+		return "", err
+	}
 
-	_, body, err := h.client.Do(ctx, req)
+	resp, body, err := h.client.Do(ctx, req)
 	if err != nil {
 		return "", err
 	}
 
+	if resp.StatusCode/100 != 2 {
+		return "", &clientError{
+			code: resp.StatusCode,
+			msg:  "bad response code",
+		}
+	}
+
 	var res struct {
 		SilenceID string `json:"silenceId"`
 	}


req, _ := http.NewRequest(http.MethodGet, u.String(), nil)

_, body, err := h.client.Do(ctx, req)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be good to check the response code. A 404 or 500 is still a successful http response, but something we want to inform the api consumers of.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a call to the local apiClient.Do() which already handles the HTTP response code and un-marshals the server's response.

Copy link
Member Author

@simonpasquier simonpasquier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! I guess that the main question is whether the new code is meant to:

  • be used as a library immediately.
  • decouple the API interactions from the CLI code (which was @josedonizetti proposition initially) but keeping it in the same package for now.

It would be good to expose this functionality so that cli and other consumers can filter their responses.

Sure. This PR was just a continuation of prometheus/client_golang#333 with minimal changes to get things started.

// See the License for the specific language governing permissions and
// limitations under the License.

package cli
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this initial change, I was following the description from #1035 where @josedonizetti said:

I propose we extract the query/parsing of the API from the amtool, and expose it properly in the cli package, moving the amtool commands to a new package inside the cli (e.g.: cli/cmd).

I'm fine with your suggestion too. The downside with this approach is that anyone wanting to use only the API client bits will have to pull the server-side dependencies too.

cli/client.go Outdated
}
for _, v := range sil.Matchers {
s.Matchers = append(s.Matchers, &types.Matcher{Name: string(v.Name), Value: v.Value, IsRegex: v.IsRegex})
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is left-over from a previous version.


req, _ := http.NewRequest(http.MethodGet, u.String(), nil)

_, body, err := h.client.Do(ctx, req)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a call to the local apiClient.Do() which already handles the HTTP response code and un-marshals the server's response.

@stuartnelson3
Copy link
Contributor

I guess leave it in cli for now, and we'll figure something out. Potentially we had lib/api or something to indicate library uses of alertmanager functionality vs. application uses.

Copy link
Contributor

@stuartnelson3 stuartnelson3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things look good, we just need to figure out a way forward with regards to the vendoring of common/model, it causes issues when trying to use this code as a library.

cli/client.go Outdated
// List returns all the active alerts.
List(ctx context.Context) ([]*model.Alert, error)
// Push sends a list of alerts to the Alertmanager.
Push(ctx context.Context, alerts ...model.Alert) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still having issues with this. The easiest thing to do is to introduce the intermediate type, and then in a separate PR we can decide if we want to replace references to model.Alert throughout the code base.

@brian-brazil what's the current state on using common/model? Are we deprecating it?

cli/client.go Outdated
// List returns all the active alerts.
List(ctx context.Context) ([]*model.Alert, error)
// Push sends a list of alerts to the Alertmanager.
Push(ctx context.Context, alerts ...model.Alert) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had success when copying over the types from common/model:

diff --git a/cli/client.go b/cli/client.go
index c623b25..9d7e0d4 100644
--- a/cli/client.go
+++ b/cli/client.go
@@ -25,7 +25,6 @@ import (
 
 	"github.com/prometheus/alertmanager/config"
 	"github.com/prometheus/alertmanager/types"
-	"github.com/prometheus/common/model"
 )
 
 const (
@@ -154,9 +153,9 @@ func (h *httpStatusAPI) Get(ctx context.Context) (*ServerStatus, error) {
 // AlertAPI provides bindings for the Alertmanager's alert API.
 type AlertAPI interface {
 	// List returns all the active alerts.
-	List(ctx context.Context) ([]*model.Alert, error)
+	List(ctx context.Context) ([]*Alert, error)
 	// Push sends a list of alerts to the Alertmanager.
-	Push(ctx context.Context, alerts ...model.Alert) error
+	Push(ctx context.Context, alerts ...Alert) error
 }
 
 // NewAlertAPI returns a new AlertAPI for the client.
@@ -168,7 +167,20 @@ type httpAlertAPI struct {
 	client api.Client
 }
 
-func (h *httpAlertAPI) List(ctx context.Context) ([]*model.Alert, error) {
+type Alert struct {
+	Labels       LabelSet  `json:"labels"`
+	Annotations  LabelSet  `json:"annotations"`
+	StartsAt     time.Time `json:"startsAt,omitempty"`
+	EndsAt       time.Time `json:"endsAt,omitempty"`
+	GeneratorURL string    `json:"generatorURL"`
+}
+
+type LabelSet map[LabelName]LabelValue
+type LabelName string
+type LabelValue string
+
+// List implements the AlertAPI interface.
+func (h *httpAlertAPI) List(ctx context.Context) ([]*Alert, error) {
 	u := h.client.URL(epAlerts, nil)
 
 	req, _ := http.NewRequest(http.MethodGet, u.String(), nil)
@@ -178,13 +190,14 @@ func (h *httpAlertAPI) List(ctx context.Context) ([]*model.Alert, error) {
 		return nil, err
 	}
 
-	var alts []*model.Alert
-	err = json.Unmarshal(body, &alts)
+	var alerts []*Alert
+	err = json.Unmarshal(body, &alerts)
 
-	return alts, err
+	return alerts, err
 }
 
-func (h *httpAlertAPI) Push(ctx context.Context, alerts ...model.Alert) error {
+// Push implements the AlertAPI interface.
+func (h *httpAlertAPI) Push(ctx context.Context, alerts ...Alert) error {
 	u := h.client.URL(epAlerts, nil)
 
 	var buf bytes.Buffer

@simonpasquier
Copy link
Member Author

I've added the following types to the cli package for now: Alert (used by the push alert API) and ExtendedAlert (used by the list alert API). I'm sure integrating this new code with the client code will require other changes down the road but I feel that pushing more stuff in this PR will complicate the review.

Copy link
Contributor

@stuartnelson3 stuartnelson3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome, thanks for all the work!

@stuartnelson3 stuartnelson3 merged commit 0c086e3 into prometheus:master Mar 28, 2018
mxinden pushed a commit to mxinden/alertmanager that referenced this pull request May 5, 2018
* cli: extract client bindings of the v1 API from amtool

This is a continuation of [1] but the code is kept in the alertmanage
repository rather than having it in client_golang.

[1] prometheus/client_golang#333

Co-Authored-By: Fabian Reinartz <fab.reinartz@gmail.com>
Co-Authored-By: Tristan Colgate <tcolgate@gmail.com>
Co-Authored-By: Corin Lawson <corin@responsight.com>
Co-Authored-By: stuart nelson <stuartnelson3@gmail.com>

* cli: fix httpSilenceAPI.Set() method

* vendor: remove github.com/prometheus/client_golang/api/alertmanager

* cli: don't use the model.Alert type
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants