Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add math operation. #8

Merged
merged 10 commits into from
May 23, 2017
Merged

Add math operation. #8

merged 10 commits into from
May 23, 2017

Conversation

sean-
Copy link
Contributor

@sean- sean- commented Apr 25, 2017

Add the math operation to perform calculations on IPs and networks. …
math takes two arguments: an operation and a string value. The two initial
operations are address and network.

The address operation's value is a decimal encoded string with a mandatory
sign. The value is added or subtracted from the IP address of the IfAddr.
Values will overflow or underflow, and if necessary, wrap at the underlying
fundamental type.

The network operation's value is a decimal encoded string with a mandatory
sign. If the value is positive it is added to the network address of the
IfAddr. If the value overflows the network size, the value is wrapped at the
network boundary (i.e. 257 will wrap in a /24 to be the same as +1). If the
value is negative it is subtracted from the broadcast address of the IfAddr.
If the value underflows the network size, the value will wrap.

The network operation is intended to address the concern raised in #7 but
in a more generic way and with IPv6 support, thereby superseding #7. For instance, to get the DNS resolver, one would simply use:

{{GetPrivateIP | math "network" "+2"}}

Thank you @taylorchu for the feature request. Can you let me know if this PR meets your needs? I know it scratches my back in terms of something that I've needed for a while now.

sean- added 6 commits April 24, 2017 01:38
`Attr` acts just like `attr`, but operates on a single `IfAddr` whereas
`attr` operates on `IfAttrs`.
`math` takes two arguments: an operation and a string value. The two initial
operations are `address` and `network`.

The `address` operation's value is a decimal encoded string with a mandatory
sign.  The value is added or subtracted from the IP address of the IfAddr.
Values will overflow or underflow, and if necessary, wrap at the underlying
fundamental type.

The `network` operation's value is a decimal encoded string with a mandatory
sign.  If the value is positive it is added to the network address of the
IfAddr.  If the value overflows the network size, the value is wrapped at the
network boundary (i.e. 257 will wrap in a /24 to be the same as +1).  If the
value is negative it is subtracted from the broadcast address of the IfAddr.
If the value underflows the network size, the value will wrap.

The `network` operation is intended to address the concern raised in hashicorp#7 but
in a more generic way.
This should be useful reference material to future eyeballs from search
engines.
@jwreagor
Copy link

jwreagor commented Apr 25, 2017

The ability to have this kind of IP math within a template is extremely helpful, especially the sorting and maps plus math. Template format can get a little unwieldy (when parsing) but totally forgiving given the derived behavior.

👍

@taylorchu
Copy link

nice! Originally I thought about doing this, but math is really a feature for more work.

{{GetPrivateIP | math "network" "+2"}} should not work because the type does not match.

@sean-
Copy link
Contributor Author

sean- commented Apr 25, 2017

@taylorchu That's correct, it doesn't work (GetPrivateIP returns a string not an IfAddrs). You have to do the following:

{{ GetPrivateInterfaces | math "network" "+2" }}

Does that work for you?

@sean-
Copy link
Contributor Author

sean- commented Apr 25, 2017

@cheapRoc Yeah, I don't like the Go template language, but I do like that "it just works" and pseudo-standard. I wish it had type support and all arguments didn't need to get passed in as strings, but I digress. There are helper functions along the way that help do the right or sane things. A GetPrivateInterface that only returns an IfAddr (vs IfAddrs) would be a nice addition, but you can get that with a GetPrivateInterfaces | sort ... | limit "1" | math "network" "+2". If there was a different way of doing this that addressed the necessary site complexities inherent in networking, I'd be all for it. LMK if you come up w/ anything. :)

@taylorchu
Copy link

sockaddr eval 'GetPrivateInterfaces | math "network" "+2" | attr "address"'

this works perfectly.

@taylorchu
Copy link

I will be nice if more helper functions work in the space of "IfAddrs/IfAddr" instead of "string" because string is simply not chainable. If we need string, we could use attr. my 2c.

@sean-
Copy link
Contributor Author

sean- commented Apr 25, 2017

@taylorchu This is one of the rubs of text/template. IfAddr/IfAddrs is a container of sorts. There should be top-level Parse, ParseIP, ParseIPv4, ParseIPv6, and ParseUNIXSocket functions that emit an IfAddr so that you could do something like:

{{ GetPrivateIP | ParseIP | Attr "address" }}

Where the chain is string -> IfAddr -> string, and the following would now work:

{{ ParseIP "127.0.0.1" | Attr "address" }}

Which would emit 127.0.0.1.

Copy link
Contributor

@slackpad slackpad left a comment

Choose a reason for hiding this comment

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

Noted a few things.

ifaddrs.go Outdated
@@ -652,6 +669,171 @@ func IfByNetwork(selectorParam string, inputIfAddrs IfAddrs) (IfAddrs, IfAddrs,
return includedIfs, excludedIfs, nil
}

// IfAddrMath will return a new IfAddr struct with a mutaterd value.
Copy link
Contributor

Choose a reason for hiding this comment

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

"mutater"?

ifaddrs.go Outdated
// underlying type.

if !signRe.MatchString(value) {
return IfAddr{}, fmt.Errorf("sign (+/-) is required operation %q", operation)
Copy link
Contributor

Choose a reason for hiding this comment

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

"is required for operation"

ifaddrs.go Outdated
// wrapping is applied.

if !signRe.MatchString(value) {
return IfAddr{}, fmt.Errorf("sign (+/-) is required operation %q", operation)
Copy link
Contributor

Choose a reason for hiding this comment

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

"required for operation"

"math": sockaddr.IfAddrsMath,

// Misc functions that operate on IfAddr input
"Attr": sockaddr.IfAttr,
Copy link
Contributor

Choose a reason for hiding this comment

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

Having Attr and attr seems super confusing - is there a better name we can give the new one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I chewed on this a bit before settling on mixed-cases. The problem is that attr accepts an IfAddrs as its argument, not an IfAddr.

The original thinking of the template functions was that sources at the start of the template pipeline would be uppercase and everything that operated on IfAddrs structs would be lowercase to indicate that they can't come first in the pipeline. e.g.

https://github.com/hashicorp/go-sockaddr/blob/master/template/template.go#L60-L71

So far this is the case, but with the math template operation, I explicitly wanted to avoid what I did with addr where I did something sketchy on the grounds that it was good UX. The behavior of attr is such that it only returns the attribute from the first element in IfAddrs. Originally I did this because I didn't want to write | limit 1 | attr "address" everywhere (history note: originally the API only had | limit 1 | join "address" "" and that experience was awful so I conjured up | attr "address" to extract an individual attribute as a string).

I was already thinking about using interface{} but I didn't want to sacrifice type safety in the library portion of the go-sockaddr and this seemed like a slippery slope:

// IfAttrs forwards the selector to IfAttrs.Attr() for resolution.  If there the
// argument passed is IfAddrs, only the first IfAddr in the slice is used.  If
// the argument is an IfAddr, it will be used.
func IfAttrs(selectorName string, ifAddrsRaw interface{}) (string, error) {
        attrName := AttrName(strings.ToLower(selectorName))
        switch v := ifAddrsRaw.(type) {
        case IfAddrs:
                if len(v) == 0 {
                        return "", nil
                }
                attrVal, err := v[0].Attr(attrName)
                return attrVal, err
        case IfAddr:
                attrVal, err := v.Attr(attrName)
                return attrVal, err
        default:
                return "", fmt.Errorf("unable to obtain attribute %s from type %T (%v)", selectorName, ifAddrsRaw, ifAddrsRaw)
        }
}

... you know what. Let me drop a facade over sockaddr.IfAttr and sockaddr.IfAddrs that does the type-unsafe interface{} type assertions.

I'm not sure why I didn't think of this earlier but I'm going to extend this idiom elsewhere to paper over various user issues (i.e. naked numbers in the template pipeline). LMK what you think now.

@slackpad slackpad merged commit efdf9bb into hashicorp:master May 23, 2017
@sean- sean- deleted the f-ipmath-add branch May 23, 2017 22:46
sean- added a commit to sean-/go-sockaddr that referenced this pull request May 23, 2017
* Adds plural IP helpers (e.g. `GetPrivateIPs`, `GetPublicIPs`)
  hashicorp#11
* Adds subnet math hashicorp#8
* Fixes helper functions for dual-homed hosts hashicorp#10)
sean- added a commit to sean-/go-sockaddr that referenced this pull request May 23, 2017
* Adds plural IP helpers (e.g. `GetPrivateIPs`, `GetPublicIPs`)
  hashicorp#11
* Adds subnet math hashicorp#8
* Fixes helper functions for dual-homed hosts hashicorp#10)
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